Option Explicit

Private Const TEST_TABLE_NAME As String = "table1"
Private Const TEST_POINT As String = "."
Private Const TEST_COL_NAME As String = "col1"
Private Const TEST_SHARE_LINE_COMMENT As String = "--aaaaaa"
Private Const TEST_SINGLE_LINE_COMMENT As String = "--bbbbb"

' ======================================================================
' 执行所有测试用例
' 这里只有格式化器的解析器中的 sql 作用域对象解析方法的测试
' ======================================================================
Public Sub test_all_case()
    ' 测试： 入口1--解析方式1: 通过 sql 字符串完成解析
    test_analyze

    ' 测试: 入口2--解析方式2: 直接解析 sql token 数组
    test_analyzeTokens

    ' 测试: 解析 sql 的驱动处理---外层逻辑 -- 包装扫描器
    test_createEnv

    ' 测试: 兜底处理--parseSqlEnvFallback--在解析失败时，强制解析
    test_parseSqlEnvFallback

    ' 测试: 解析 sql 的 token 并生成作用域---生成sql作用域的基础驱动--剥离左括号
    test_parseSqlEnv

    ' 测试: 解析并吸收union开始的作用域
    test_parseFromUnionEnv

    ' 测试: 解析sql并生成基础作用域--剥离with
    test_parseBaseEnv

    ' 测试: 解析完整的 with 作用域---外层逻辑 -- 进行括号配对
    test_parseWithEnv

    ' 测试: 解析一个with表作用域
    test_parseWithTableEnv

    ' 解析with表作用域中的列名
    test_parseWithTableColNames

    ' 测试: 解析 select sql片段，并生成作用域
    ' 基础结构测试
    test_parseSelect_base

    ' 测试: 解析 select sql片段，并生成作用域
    ' 基础 if else 分支结构
    test_parseSelect_if_else

    ' 测试: 各类型sql的组成部分的通用解析(暂时只解析 select 作用域)
    test_parseSqlComposition_select

    ' 解析select关键字及其筛选项
    test_parseSelectItemsEnv

    ' 测试: 处理 select 筛选项
    test_parseSelectItemInfo

    ' 测试: 吸收列后面的别名
    test_parseColAliasName

    ' 测试: 解析update的set作用域
    test_parseUpdateSetEnv

    ' 测试: 解析 using
    test_parseUsingEnv

    ' 测试: 解析 delete sql片段，并生成作用域
    test_parseDelete

    ' 测试: 解析delete作用域的起始部分
    test_parseDeleteStartEnv

    ' 测试: 解析 inset sql片段，并生成作用域
    test_parseInsert

    ' 测试: 解析insert作用域的起始部分
    test_parseInsertStartEnv

    ' 测试: 解析 into 作用域
    test_parseIntoEnv

    ' 测试: 解析insert的rowAlias
    test_parseRowAliasEnv

    ' 测试: 解析insert的value
    test_parseInsetValuesEnv

    ' 测试: 解析赋值语句列表作用域
    test_parseAssignmentListEnv

    ' 测试: 解析ON_DUPLICATE作用域
    test_parseInsertOnDuplicateEnv

    ' 测试: 解析 from
    test_parseFromEnv

    ' 测试: 解析 udpate sql片段，并生成作用域
    test_parseUpdate

    ' 测试: 解析update关键字及其下的所有表定义
    test_parseUpdateTableReferencesEnv

    ' 测试: 解析 join
    test_parseJoinEnv

    ' 测试: 解析join关键字
    test_parseJoinKey

    ' 测试: 解析连续出现的多个表定义
    test_parseTableReferences

    ' 测试: 匹配一个表定义
    test_parseTableFactor

    ' 测试: 匹配一个表名称定义开始的表定义
    test_parseTableFactorFromTableName

    ' 测试: 匹配一个子查询开始的表定义
    test_parseTableFactorFromSubQuery

    ' 测试: 匹配一个表名称定义
    test_parseTableName

    ' 测试: 匹配 partition
    test_parsePartition

    ' 测试: 匹配 index hint list
    test_parseIndexHintList

    ' 测试: 匹配index_hint
    test_parseIndexHint

    ' 测试: 解析条件作用域
    test_parseConditionEnv

    ' 测试: 匹配一个赋值语句
    test_parseAssignment

    ' 测试: 匹配 limit
    test_parseLimitEnv

    ' 测试: 匹配order by
    test_parseOrderByEnv

    ' 测试: 匹配 group by
    test_parseGroupByEnv
End Sub

' ======================================================================
' 测试通用处理
' ======================================================================

' 创建一个格式化器的 sql 解析器
Private Function createSqlFormatterAnalyzer() As SqlFormatterAnalyzer
    Dim config As SqlFormatterConfiguration
    Set config = New SqlFormatterConfiguration

    Dim result As SqlFormatterAnalyzer
    Set result = New SqlFormatterAnalyzer

    result.Init config

    Set createSqlFormatterAnalyzer = result
End Function

' 创建一个扫描器
Private Function getObjArrayRefScanner(ParamArray tokens() As Variant) As ObjArrayRefScanner
    Dim i As Integer
    Dim t As SqlToken

    Dim tokenCount As Integer
    tokenCount = UBound(tokens)

    ' 创建一个和参数数量相同的数组引用
    Dim meanTokens As IObjArrayRef
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(tokenCount)

    ' 遍历参数，并保持到数组引用
    For i = 0 To UBound(tokens)
        Set t = tokens(i)

        meanTokens.Add t
    Next

    ' 创建扫描器
    Dim scanner As ObjArrayRefScanner
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' return scanner
    Set getObjArrayRefScanner = scanner
End Function

' 向一个数组引用中添加元素
Private Sub addElemToArray(ByRef arr As IObjArrayRef, ParamArray tokens() As Variant)
    Dim i As Integer
    Dim t As SqlToken

    Dim tokenCount As Integer
    tokenCount = UBound(tokens)

    ' 遍历参数，并保持到数组引用
    For i = 0 To UBound(tokens)
        Set t = tokens(i)

        arr.Add t
    Next
End Sub

' 获取一个 [;] 元素
Private Function getSemicolonToken() As SqlToken
    Set getSemicolonToken = New SqlToken
    getSemicolonToken.Init ";", SqlTokenType.SEMICOLON
End Function


' 获取一个 recursive 元素
Private Function getRecursiveToken() As SqlToken
    Set getRecursiveToken = New SqlToken
    getRecursiveToken.Init "recursive", SqlTokenType.KEY_RECURSIVE
End Function

' 获取一个 from 元素
Private Function getFromToken() As SqlToken
    Set getFromToken = New SqlToken
    getFromToken.Init "from", SqlTokenType.KEY_FROM
End Function

' 获取一个 using 元素
Private Function getUsingToken() As SqlToken
    Set getUsingToken = New SqlToken
    getUsingToken.Init "using", SqlTokenType.KEY_USING
End Function

' 获取一个 cast 元素
Private Function getCastToken() As SqlToken
    Set getCastToken = New SqlToken
    getCastToken.Init "cast", SqlTokenType.KEY_CAST
End Function

' 创建一个字符串 token
Private Function createStrToken(ByVal v As String) As SqlToken
    Set createStrToken = New SqlToken
    createStrToken.Init "'" & v & "'", SqlTokenType.STR_STR
End Function

' where 关键字
Private Function getWhereToken() As SqlToken
    Set getWhereToken = New SqlToken
    getWhereToken.Init "where", SqlTokenType.KEY_WHERE
End Function

' having 关键字
Private Function getHavingToken() As SqlToken
    Set getHavingToken = New SqlToken
    getHavingToken.Init "having", SqlTokenType.KEY_HAVING
End Function

' on 关键字
Private Function getOnToken() As SqlToken
    Set getOnToken = New SqlToken
    getOnToken.Init "on", SqlTokenType.KEY_ON
End Function

' case 运算符
Private Function getCaseToken() As SqlToken
    Set getCaseToken = New SqlToken
    getCaseToken.Init "case", SqlTokenType.KEY_CASE
End Function

' when 运算符
Private Function getWhenToken() As SqlToken
    Set getWhenToken = New SqlToken
    getWhenToken.Init "when", SqlTokenType.KEY_WHEN
End Function

' then 运算符
Private Function getThenToken() As SqlToken
    Set getThenToken = New SqlToken
    getThenToken.Init "then", SqlTokenType.KEY_THEN
End Function

' else 运算符
Private Function getElseToken() As SqlToken
    Set getElseToken = New SqlToken
    getElseToken.Init "else", SqlTokenType.KEY_ELSE
End Function

' end 运算符
Private Function getEndToken() As SqlToken
    Set getEndToken = New SqlToken
    getEndToken.Init "end", SqlTokenType.KEY_END
End Function

' ASC 运算符
Private Function getAscToken() As SqlToken
    Set getAscToken = New SqlToken
    getAscToken.Init "asc", SqlTokenType.KEY_ASC
End Function

' DESC 运算符
Private Function getDescToken() As SqlToken
    Set getDescToken = New SqlToken
    getDescToken.Init "desc", SqlTokenType.KEY_DESC
End Function

' || 运算符
Private Function getStrConcatToken() As SqlToken
    Set getStrConcatToken = New SqlToken
    getStrConcatToken.Init "||", SqlTokenType.OPERA_STR_CONCAT
End Function

' + 运算符
Private Function getPlusToken() As SqlToken
    Set getPlusToken = New SqlToken
    getPlusToken.Init "+", SqlTokenType.OPERA_PLUS
End Function

' - 运算符
Private Function getMinusToken() As SqlToken
    Set getMinusToken = New SqlToken
    getMinusToken.Init "-", SqlTokenType.OPERA_MINUS
End Function

' 创建一个数值元素
Private Function createNumToken(ByVal v As String) As SqlToken
    Set createNumToken = New SqlToken
    createNumToken.Init v, SqlTokenType.STR_NUM
End Function

' 获取一个 true 元素
Private Function getTrueToken() As SqlToken
    Set getTrueToken = New SqlToken
    getTrueToken.Init "true", SqlTokenType.KEY_TRUE
End Function

' 获取一个 false 元素
Private Function getFalseToken() As SqlToken
    Set getFalseToken = New SqlToken
    getFalseToken.Init "false", SqlTokenType.KEY_FALSE
End Function

' 获取一个 null 元素
Private Function getNullToken() As SqlToken
    Set getNullToken = New SqlToken
    getNullToken.Init "null", SqlTokenType.KEY_NULL
End Function

' 获取 * 元素
Private Function getAsteriskToken() As SqlToken
    Set getAsteriskToken = New SqlToken
    getAsteriskToken.Init "*", SqlTokenType.ASTERISK
End Function

' 获取 binary 关键字
Private Function getBinaryToken() As SqlToken
    Set getBinaryToken = New SqlToken
    getBinaryToken.Init "binary", SqlTokenType.KEY_BINARY
End Function

' 获取 order by 关键字
Private Function getOrderByToken() As SqlToken
    Set getOrderByToken = New SqlToken
    getOrderByToken.Init "order by", SqlTokenType.KEY_ORDER_BY
End Function

' 获取 group by 关键字
Private Function getGroupByToken() As SqlToken
    Set getGroupByToken = New SqlToken
    getGroupByToken.Init "group by", SqlTokenType.KEY_GROUP_BY
End Function

' 获取 with 关键字
Private Function getWithToken() As SqlToken
    Set getWithToken = New SqlToken
    getWithToken.Init "with", SqlTokenType.KEY_WITH
End Function

' 获取 ROLLUP 关键字
Private Function getRollupToken() As SqlToken
    Set getRollupToken = New SqlToken
    getRollupToken.Init "rollup", SqlTokenType.KEY_ROLLUP
End Function

' 获取 offset 关键字
Private Function getOffsetToken() As SqlToken
    Set getOffsetToken = New SqlToken
    getOffsetToken.Init "offset", SqlTokenType.KEY_OFFSET
End Function

' 获取 limit 关键字
Private Function getLimitToken() As SqlToken
    Set getLimitToken = New SqlToken
    getLimitToken.Init "limit", SqlTokenType.KEY_LIMIT
End Function

' 获取 is 关键字
Private Function getIsToken() As SqlToken
    Set getIsToken = New SqlToken
    getIsToken.Init "is", SqlTokenType.KEY_IS
End Function

' 获取 not 关键字
Private Function getNotToken() As SqlToken
    Set getNotToken = New SqlToken
    getNotToken.Init "not", SqlTokenType.KEY_NOT
End Function

' 获取 union 关键字
Private Function getUnionToken() As SqlToken
    Set getUnionToken = New SqlToken
    getUnionToken.Init "union", SqlTokenType.KEY_UNION
End Function

' 获取 select 关键字
Private Function getSelectToken() As SqlToken
    Set getSelectToken = New SqlToken
    getSelectToken.Init "select", SqlTokenType.KEY_SELECT
End Function

' 获取 update 关键字
Private Function getUpdateToken() As SqlToken
    Set getUpdateToken = New SqlToken
    getUpdateToken.Init "update", SqlTokenType.KEY_UPDATE
End Function

' 获取 delete 关键字
Private Function getDeleteToken() As SqlToken
    Set getDeleteToken = New SqlToken
    getDeleteToken.Init "delete", SqlTokenType.KEY_DELETE
End Function

' 获取 quick 关键字
Private Function getQuickToken() As SqlToken
    Set getQuickToken = New SqlToken
    getQuickToken.Init "quick", SqlTokenType.KEY_QUICK
End Function

' 获取 set 关键字
Private Function getSetToken() As SqlToken
    Set getSetToken = New SqlToken
    getSetToken.Init "set", SqlTokenType.KEY_SET
End Function

' 获取 LOW_PRIORITY 关键字
Private Function getLowPriorityToken() As SqlToken
    Set getLowPriorityToken = New SqlToken
    getLowPriorityToken.Init "low_priority", SqlTokenType.KEY_LOW_PRIORITY
End Function

' 获取 distinct 关键字
Private Function getDistinctToken() As SqlToken
    Set getDistinctToken = New SqlToken
    getDistinctToken.Init "distinct", SqlTokenType.KEY_DISTINCT
End Function

' 获取 all 关键字
Private Function getAllToken() As SqlToken
    Set getAllToken = New SqlToken
    getAllToken.Init "all", SqlTokenType.KEY_ALL
End Function

' 获取 join 关键字
Private Function getJoinToken() As SqlToken
    Set getJoinToken = New SqlToken
    getJoinToken.Init "join", SqlTokenType.KEY_JOIN
End Function

' 获取 left 关键字
Private Function getLeftToken() As SqlToken
    Set getLeftToken = New SqlToken
    getLeftToken.Init "left", SqlTokenType.KEY_LEFT
End Function

' 获取 right 关键字
Private Function getRightToken() As SqlToken
    Set getRightToken = New SqlToken
    getRightToken.Init "right", SqlTokenType.KEY_RIGHT
End Function

' 获取 inner 关键字
Private Function getInnerToken() As SqlToken
    Set getInnerToken = New SqlToken
    getInnerToken.Init "inner", SqlTokenType.KEY_INNER
End Function

' 获取 cross 关键字
Private Function getCrossToken() As SqlToken
    Set getCrossToken = New SqlToken
    getCrossToken.Init "cross", SqlTokenType.KEY_CROSS
End Function

' 获取 out 关键字
Private Function getOuterToken() As SqlToken
    Set getOuterToken = New SqlToken
    getOuterToken.Init "outer", SqlTokenType.KEY_OUTER
End Function

' 获取 use 关键字
Private Function getUseToken() As SqlToken
    Set getUseToken = New SqlToken
    getUseToken.Init "use", SqlTokenType.KEY_USE
End Function

' 获取 index 关键字
Private Function getIndexToken() As SqlToken
    Set getIndexToken = New SqlToken
    getIndexToken.Init "index", SqlTokenType.KEY_INDEX
End Function

' 获取 ignore 关键字
Private Function getIgnoreToken() As SqlToken
    Set getIgnoreToken = New SqlToken
    getIgnoreToken.Init "ignore", SqlTokenType.KEY_IGNORE
End Function

' 获取 key 关键字
Private Function getKeyToken() As SqlToken
    Set getKeyToken = New SqlToken
    getKeyToken.Init "key", SqlTokenType.KEY_KEY
End Function

' 获取 for 关键字
Private Function getForToken() As SqlToken
    Set getForToken = New SqlToken
    getForToken.Init "for", SqlTokenType.KEY_FOR
End Function

' 获取 force 关键字
Private Function getForceToken() As SqlToken
    Set getForceToken = New SqlToken
    getForceToken.Init "force", SqlTokenType.KEY_FORCE
End Function

' 获取 as 关键字
Private Function getAsToken() As SqlToken
    Set getAsToken = New SqlToken
    getAsToken.Init "as", SqlTokenType.KEY_AS
End Function

' 获取左括号
Private Function getLeftParenthesisToken() As SqlToken
    Set getLeftParenthesisToken = New SqlToken
    getLeftParenthesisToken.Init "(", SqlTokenType.LEFT_PARENTHESIS
End Function

' 获取右括号
Private Function getRightParenthesisToken() As SqlToken
    Set getRightParenthesisToken = New SqlToken
    getRightParenthesisToken.Init ")", SqlTokenType.RIGHT_PARENTHESIS
End Function

' 获取 partition 关键字
Private Function getPartitionToken() As SqlToken
    Set getPartitionToken = New SqlToken
    getPartitionToken.Init "partition", SqlTokenType.KEY_PARTITION
End Function

' 获取逻辑运算符: and
Private Function getAndToken() As SqlToken
    Set getAndToken = New SqlToken
    getAndToken.Init "and", SqlTokenType.KEY_AND
End Function

' 获取一个条件运算符: =
Private Function getEqConditionOperator() As SqlToken
    Set getEqConditionOperator = New SqlToken
    getEqConditionOperator.Init "=", SqlTokenType.OPERA_EQ
End Function

' 获取一个条件运算符: <=
Private Function getLeConditionOperator() As SqlToken
    Set getLeConditionOperator = New SqlToken
    getLeConditionOperator.Init "<=", SqlTokenType.OPERA_LE
End Function

' 获取一个任意名称的 STR_NAME token
Private Function getStrNameToken(ByRef s As String) As SqlToken
    Set getStrNameToken = New SqlToken
    getStrNameToken.Init s, SqlTokenType.STR_NAME
End Function

' 获取表名 token
Private Function getTableNameToken() As SqlToken
    Set getTableNameToken = New SqlToken
    getTableNameToken.Init TEST_TABLE_NAME, SqlTokenType.STR_NAME
End Function

' 获取 [.] token
Private Function getPointToken() As SqlToken
    Set getPointToken = New SqlToken
    getPointToken.Init TEST_POINT, SqlTokenType.POINT
End Function

' 获取列名 token
Private Function getColNameToken() As SqlToken
    Set getColNameToken = New SqlToken
    getColNameToken.Init TEST_COL_NAME, SqlTokenType.STR_NAME
End Function

' 创建和前一个元素在同一行的注释
Private Function createShareLineCommentToken(ByRef v As String) As SqlToken
    Set createShareLineCommentToken = New SqlToken
    createShareLineCommentToken.Init v, SqlTokenType.SHARE_LINE_COMMENT
End Function

' 获取一个单行的注释
Private Function createSingleLineCommentToken(ByRef v As String) As SqlToken
    Set createSingleLineCommentToken = New SqlToken
    createSingleLineCommentToken.Init v, SqlTokenType.SINGLE_LINE_COMMENT
End Function

' 获取和前一个元素在同一行的注释
Private Function getShareLineCommentToken() As SqlToken
    Set getShareLineCommentToken = New SqlToken
    getShareLineCommentToken.Init TEST_SHARE_LINE_COMMENT, SqlTokenType.SHARE_LINE_COMMENT
End Function

' 获取一个单行的注释
Private Function getSingleLineCommentToken() As SqlToken
    Set getSingleLineCommentToken = New SqlToken
    getSingleLineCommentToken.Init TEST_SINGLE_LINE_COMMENT, SqlTokenType.SINGLE_LINE_COMMENT
End Function

' 获取一个逗号的元素
Private Function getCommaToken() As SqlToken
    Set getCommaToken = New SqlToken
    getCommaToken.Init ",", SqlTokenType.COMMA
End Function

' 生成一个函数名称元素
Private Function createFuncNameToken(ByVal funcName As String) As SqlToken
    Set createFuncNameToken = New SqlToken
    createFuncNameToken.Init funcName, SqlTokenType.STR_FUNC
End Function

' 获取 CURRENT_TIMESTAMP 元素
Private Function getCurrentTimestampToken() As SqlToken
    Set getCurrentTimestampToken = New SqlToken
    getCurrentTimestampToken.Init "CURRENT_TIMESTAMP", SqlTokenType.CONST_CURRENT_TIMESTAMP
End Function

' 获取 exists 元素
Private Function getExistsToken() As SqlToken
    Set getExistsToken = New SqlToken
    getExistsToken.Init "exists", SqlTokenType.KEY_EXISTS
End Function

' 获取 HIGH_PRIORITY 关键字
Private Function getHighPriorityToken() As SqlToken
    Set getHighPriorityToken = New SqlToken
    getHighPriorityToken.Init "high_priority", SqlTokenType.KEY_HIGH_PRIORITY
End Function

' 获取 delayed 关键字
Private Function getDelayedToken() As SqlToken
    Set getDelayedToken = New SqlToken
    getDelayedToken.Init "delayed", SqlTokenType.KEY_DELAYED
End Function

' 获取 insert 关键字
Private Function getInsertToken() As SqlToken
    Set getInsertToken = New SqlToken
    getInsertToken.Init "insert", SqlTokenType.KEY_INSERT
End Function

' 获取 into 关键字
Private Function getIntoToken() As SqlToken
    Set getIntoToken = New SqlToken
    getIntoToken.Init "into", SqlTokenType.KEY_INTO
End Function

' 获取 value 关键字
Private Function getValueToken() As SqlToken
    Set getValueToken = New SqlToken
    getValueToken.Init "value", SqlTokenType.KEY_VALUE
End Function

' 获取 values 关键字
Private Function getValuesToken() As SqlToken
    Set getValuesToken = New SqlToken
    getValuesToken.Init "values", SqlTokenType.KEY_VALUES
End Function

' 获取 duplicate 关键字
Private Function getDuplicateToken() As SqlToken
    Set getDuplicateToken = New SqlToken
    getDuplicateToken.Init "duplicate", SqlTokenType.KEY_DUPLICATE
End Function

' ======================================================================
' Sql 解析方法的测试
' ======================================================================

' 测试： 入口1--解析方式1: 通过 sql 字符串完成解析
Private Sub test_analyze()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim sqlStr As String
    Dim sqlEnvs As IObjArrayRef

    ' ==============================
    ' 1. 测试: 所有 token 都被使用，并解析成一个作用域
    '    select + STR_NAME
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 sql 字符串
    sqlStr = "select x1 from x2"

    ' 2. 执行测试方法
    analyzer.analyze sqlStr
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 1

    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' ==============================
    ' 2. 测试: 所有 token 都被使用，并解析成一个作用域 + 作用域以 [;] 结尾
    '    select + STR_NAME
    '    + from + STR_NAME + ;
    ' ==============================
    ' 1. 创建 sql 字符串
    sqlStr = "select x1 from x2;"

    ' 2. 执行测试方法
    analyzer.analyze sqlStr
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 1

    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' ==============================
    ' 3. 测试: 所有 token 都被使用 + 多个作用域 + 最后不是以 [;] 结尾
    '    select + STR_NAME
    '    + from + STR_NAME + ;
    '    + select + STR_NAME
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 sql 字符串
    sqlStr = "select x1 from x2;select x3 from x4"

    ' 2. 执行测试方法
    analyzer.analyze sqlStr
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 2

    ' 3.1 检查第一个解析结果
    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' 3.2 检查第二个解析结果
    Set elem = sqlEnvs.Item(1)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x3"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x4"

    ' ==============================
    ' 4. 测试: 所有 token 都被使用 + 多个作用域 + 最后不是以 [;] 结尾
    '    select + STR_NAME
    '    + from + STR_NAME + ;
    '    + select + STR_NAME
    '    + from + STR_NAME + ;
    ' ==============================
    ' 1. 创建 sql 字符串
    sqlStr = "select x1 from x2;select x3 from x4;"

    ' 2. 执行测试方法
    analyzer.analyze sqlStr
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 2

    ' 3.1 检查第一个解析结果
    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' 3.2 检查第二个解析结果
    Set elem = sqlEnvs.Item(1)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x3"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x4"

    ' ==============================
    ' 5. 测试: 一个 select 作用域 + STR_NAME (使用兜底方法解析) + 一个 select 作用域
    '    select + STR_NAME
    '    + from + STR_NAME + where a < b and c between x to y;
    '    + STR_NAME + ;
    '    + select + STR_NAME
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 sql 字符串
    sqlStr = "select x1 from x2 where a < b and c between x and y;abc;select x3 from x4"

    ' 2. 执行测试方法
    analyzer.analyze sqlStr
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 3

    ' 3.1 检查第一个解析结果
    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 7
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"
    Debug.Assert strArr.Item(4)  = "where"
    Debug.Assert strArr.Item(5)  = "        a < b"
    Debug.Assert strArr.Item(6)  = "    and c between x and y"

    ' 3.2 检查第2个解析结果
    Set elem = sqlEnvs.Item(1)
    Debug.Assert elem.getType = SqlTokenType.STR_NAME

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0)  = "abc"

    ' 3.3 检查第3个解析结果
    Set elem = sqlEnvs.Item(2)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x3"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x4"

    ' ==============================
    ' 6. 测试: 一个 select 作用域 + STR_NAME (使用兜底方法解析) + 一个 select 作用域
    '         + 能够解析注释
    '    select + STR_NAME
    '    + from + STR_NAME + 一个注释 + ;
    '    + STR_NAME + 一个注释 + ;
    '    + select + STR_NAME + 一个注释
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 sql 字符串
    sqlStr = "select x1 from x2 --c1" & vbCrLf & _
             ";" & vbCrLf & _
             "abc --c2" & vbCrLf & _
             ";" & vbCrLf & _
             "select x3 --c3" & vbCrLf & _
             "from x4"

    ' 2. 执行测试方法
    analyzer.analyze sqlStr
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 3

    ' 3.1 检查第一个解析结果
    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2 --c1"

    ' 3.2 检查第2个解析结果
    Set elem = sqlEnvs.Item(1)
    Debug.Assert elem.getType = SqlTokenType.STR_NAME

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0)  = "abc --c2"

    ' 3.3 检查第3个解析结果
    Set elem = sqlEnvs.Item(2)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x3 --c3"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x4"

    ' ==============================
    ' 7. 测试: 一个 select 作用域 + STR_NAME (使用兜底方法解析) + 一个 select 作用域
    '         + 能够解析注释
    '    select + STR_NAME
    '    + from + STR_NAME + 一个注释 + ;
    '    + STR_NAME + 一个注释 + ;
    '    + select + STR_NAME + 一个注释
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 sql 字符串
    sqlStr = "select x3 --c3" & vbCrLf & _
             "from" & vbCrLf & _
             "  (" & vbCrLf & _
             "      select *" & vbCrLf & _
             "      from table1 t1" & vbCrLf & _
             "      where" & vbCrLf & _
             "          a < b" & vbCrLf & _
             "          and a <= b" & vbCrLf & _
             "  ) as tx" & vbCrLf & _
             "where" & vbCrLf & _
             "          a = b" & vbCrLf & _
             "      and a = c"
    sqlStr = sqlStr & vbCrLf & _
            "    and case" & vbCrLf & _
            "        when y then b" & vbCrLf & _
            "        else c" & vbCrLf & _
            "    end" & vbCrLf & _
            "    <= case" & vbCrLf & _
            "        when y then b" & vbCrLf & _
            "        else c" & vbCrLf & _
            "    end"

    ' 2. 执行测试方法
    analyzer.analyze sqlStr
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 1

    ' 3.1 检查第一个解析结果
    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 24
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x3 --c3"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    ("
    Debug.Assert strArr.Item(4)  = "        select"
    Debug.Assert strArr.Item(5)  = "            *"
    Debug.Assert strArr.Item(6)  = "        from"
    Debug.Assert strArr.Item(7)  = "            table1 t1"
    Debug.Assert strArr.Item(8)  = "        where"
    Debug.Assert strArr.Item(9)  = "                a < b"
    Debug.Assert strArr.Item(10) = "            and a <= b"
    Debug.Assert strArr.Item(11) = "    ) as tx"
    Debug.Assert strArr.Item(12) = "where"
    Debug.Assert strArr.Item(13) = "        a = b"
    Debug.Assert strArr.Item(14) = "    and a = c"
    Debug.Assert strArr.Item(15) = "    and case"
    Debug.Assert strArr.Item(16) = "            when y then b"
    Debug.Assert strArr.Item(17) = "            else c"
    Debug.Assert strArr.Item(18) = "        end"
    Debug.Assert strArr.Item(19) = "        <="
    Debug.Assert strArr.Item(20) = "        case"
    Debug.Assert strArr.Item(21) = "            when y then b"
    Debug.Assert strArr.Item(22) = "            else c"
    Debug.Assert strArr.Item(23) = "        end"

    ' ==============================
    ' 8. 测试: 空字符串，返回 null
    ' ==============================
    ' 1. 创建 sql 字符串
    sqlStr = ""

    ' 2. 执行测试方法
    analyzer.analyze sqlStr
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs Is Nothing
End Sub

' 测试: 入口2--解析方式2: 直接解析 sql token 数组
Private Sub test_analyzeTokens()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef
    Dim sqlEnvs As IObjArrayRef

    ' ==============================
    ' 1. 测试: 所有 token 都被使用，并解析成一个作用域
    '    select + STR_NAME
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("x2")

    ' 2. 执行测试方法
    analyzer.analyzeTokens meanTokens
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 1

    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' ==============================
    ' 2. 测试: 所有 token 都被使用，并解析成一个作用域 + 作用域以 [;] 结尾
    '    select + STR_NAME
    '    + from + STR_NAME + ;
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("x2"), getSemicolonToken

    ' 2. 执行测试方法
    analyzer.analyzeTokens meanTokens
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 1

    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' ==============================
    ' 3. 测试: 所有 token 都被使用 + 多个作用域 + 最后不是以 [;] 结尾
    '    select + STR_NAME
    '    + from + STR_NAME + ;
    '    + select + STR_NAME
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("x2"), getSemicolonToken, _
        getSelectToken, getStrNameToken("x3"), _
        getFromToken, getStrNameToken("x4")

    ' 2. 执行测试方法
    analyzer.analyzeTokens meanTokens
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 2

    ' 3.1 检查第一个解析结果
    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' 3.2 检查第二个解析结果
    Set elem = sqlEnvs.Item(1)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x3"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x4"

    ' ==============================
    ' 4. 测试: 所有 token 都被使用 + 多个作用域 + 最后不是以 [;] 结尾
    '    select + STR_NAME
    '    + from + STR_NAME + ;
    '    + select + STR_NAME
    '    + from + STR_NAME + ;
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("x2"), getSemicolonToken, _
        getSelectToken, getStrNameToken("x3"), _
        getFromToken, getStrNameToken("x4"), getSemicolonToken

    ' 2. 执行测试方法
    analyzer.analyzeTokens meanTokens
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 2

    ' 3.1 检查第一个解析结果
    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' 3.2 检查第二个解析结果
    Set elem = sqlEnvs.Item(1)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x3"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x4"

    ' ==============================
    ' 5. 测试: 一个 select 作用域 + STR_NAME (使用兜底方法解析) + 一个 select 作用域
    '    select + STR_NAME
    '    + from + STR_NAME + ;
    '    + STR_NAME + ;
    '    + select + STR_NAME
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("x2"), getSemicolonToken, _
        _
        getStrNameToken("abc"), getSemicolonToken, _
        _
        getSelectToken, getStrNameToken("x3"), _
        getFromToken, getStrNameToken("x4"), getSemicolonToken

    ' 2. 执行测试方法
    analyzer.analyzeTokens meanTokens
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs.Count = 3

    ' 3.1 检查第一个解析结果
    Set elem = sqlEnvs.Item(0)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' 3.2 检查第2个解析结果
    Set elem = sqlEnvs.Item(1)
    Debug.Assert elem.getType = SqlTokenType.STR_NAME

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0)  = "abc"

    ' 3.3 检查第3个解析结果
    Set elem = sqlEnvs.Item(2)
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x3"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x4"

    ' ==============================
    ' 6. 测试: 空元素数组，返回 null
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(1)

    ' 2. 执行测试方法
    analyzer.analyzeTokens meanTokens
    Set sqlEnvs = analyzer.getSqlEnvs

    ' 3. 检查结果
    Debug.Assert sqlEnvs Is Nothing
End Sub

' 测试: 解析 sql 的驱动处理---外层逻辑 -- 包装扫描器
Private Sub test_createEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 测试: 所有 token 都被使用，并解析成一个作用域
    '    select + STR_NAME
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("x2")

    ' 2. 执行测试方法
    Set elem = analyzer.createEnv(meanTokens)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' ==============================
    ' 2. 测试: 所有 token 都被使用，并生成两个作用域
    '     select + STR_NAME
    '     + from + STR_NAME
    '     + select + STR_NAME
    '     + from + STR_NAME
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("x2"), _
        getSelectToken, getStrNameToken("x3"), _
        getFromToken, getStrNameToken("x4")

    ' 2. 执行测试方法
    Set elem = analyzer.createEnv(meanTokens)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.NO_MEAN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 8
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    x2"
    Debug.Assert strArr.Item(4)  = "select"
    Debug.Assert strArr.Item(5)  = "    x3"
    Debug.Assert strArr.Item(6)  = "from"
    Debug.Assert strArr.Item(7)  = "    x4"

    ' ==============================
    ' 3. 测试: 存在无法正常解析，需要由兜底方法来解析的内容
    '    select + STR_NAME
    '    + from + STR_NAME + STR_NAME(这个是表别名)
    '    + STR_NAME(这个是需要由兜底方法来解析的内容)
    '    + [,](这个是需要由兜底方法来解析的内容)
    '    select + STR_NAME
    '    + from + STR_NAME
    ' ==============================
    ' 1. 创建 token 集合
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("table1"), getStrNameToken("t1"), _
        _
        getStrNameToken("abc"), _
        getCommaToken, _
        _
        getSelectToken, getStrNameToken("x2"), _
        getFromToken, getStrNameToken("table2")
    ' 2. 执行测试方法
    Set elem = analyzer.createEnv(meanTokens)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.NO_MEAN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 10
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    table1 t1"
    Debug.Assert strArr.Item(4)  = "abc"
    Debug.Assert strArr.Item(5)  = ","
    Debug.Assert strArr.Item(6)  = "select"
    Debug.Assert strArr.Item(7)  = "    x2"
    Debug.Assert strArr.Item(8)  = "from"
    Debug.Assert strArr.Item(9)  = "    table2"
End Sub

' 测试: 兜底处理--parseSqlEnvFallback--在解析失败时，强制解析
Private Sub test_parseSqlEnvFallback()

    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: 一个同行注释 + UNION
    '    测试: 开头是一个同行注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(createShareLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlToken
    Debug.Assert elem.toString(config) = "--c1"
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 2. 扫描器内: 一个单行注释 + UNION
    '    测试: 开头是一个单行注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(createSingleLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlToken
    Debug.Assert elem.toString(config) = "--c1"
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 3. 扫描器内: ) + UNION
    '    测试: 开头是一个右括号
    ' ==============================
    ' 1. 创建扫描器
    Set token = getRightParenthesisToken
    Set scanner = getObjArrayRefScanner(token, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 4. 扫描器内: ) + ) + )
    '    测试: 只有左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        getLeftParenthesisToken, getLeftParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0)  = "("
    Debug.Assert strArr.Item(1)  = "("
    Debug.Assert strArr.Item(2)  = "("

    ' ==============================
    ' 5. 扫描器内: select + STR_NAME + )
    '    测试: 解析 select
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    x1"

    ' ==============================
    ' 6. 扫描器内: from + STR_NAME + )
    '    测试: 解析 from
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getFromToken, getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "from"
    Debug.Assert strArr.Item(1)  = "    x1"

    ' ==============================
    ' 7. 扫描器内: where + STR_NAME + )
    '    测试: 解析 where
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "where"
    Debug.Assert strArr.Item(1)  = "    x1"

    ' ==============================
    ' 8. 扫描器内: left + join + STR_NAMR + )
    '    测试: 解析 join
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftToken, getJoinToken, getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "left join"
    Debug.Assert strArr.Item(1)  = "    x1"

    ' ==============================
    ' 9. 扫描器内: on + STR_NAMR + )
    '    测试: 解析 on
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "on"
    Debug.Assert strArr.Item(1)  = "    x1"

    ' ==============================
    ' 10. 扫描器内: groupby + STR_NAMR + )
    '    测试: 解析 groupby
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "group by"
    Debug.Assert strArr.Item(1)  = "    x1"

    ' ==============================
    ' 11. 扫描器内: having + STR_NAMR + )
    '    测试: 解析 having
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getHavingToken, getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "having"
    Debug.Assert strArr.Item(1)  = "    x1"

    ' ==============================
    ' 12. 扫描器内: orderby + STR_NAMR + )
    '    测试: 解析 orderby
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "order by"
    Debug.Assert strArr.Item(1)  = "    x1"

    ' ==============================
    ' 13. 扫描器内: limit + STR_NUM + )
    '    测试: 解析 limit
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLimitToken, createNumToken("123"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Debug.Assert elem.toString(config)  = "limit 123"

    ' ==============================
    ' 14. 扫描器内:
    '            union
    '            select c2
    '            from t2
    '            )
    '     无左括号栈
    ' 测试: 解析 union
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken, _
        getSelectToken, getStrNameToken("c2"), _
        getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0)  = "union"
    Debug.Assert strArr.Item(1)  = "select"
    Debug.Assert strArr.Item(2)  = "    c2"

    ' ==============================
    ' 15. 扫描器内: 一个完整的 index_hint + KEY_UNION
    '    测试: 解析 index_hint
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getUseToken, getKeyToken, getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for group by (x1, x2)"

    ' ==============================
    ' 16. 扫描器内: cast + ( + STR_NAME + ) + KEY_UNION
    '    测试: 解析 cast 函数
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getCastToken, _
        getLeftParenthesisToken, getStrNameToken("x1"), getRightParenthesisToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.toString(config)  = "cast(x1)"

    ' ==============================
    ' 17. 扫描器内: as + KEY_UNION
    '    测试: 解析 as
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getAsToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.toString(config)  = "as"

    ' ==============================
    ' 18. 扫描器内: and + STR_NAME + <= STR_NAME + KEY_UNION
    '    测试: 解析 条件运算符开始的内容
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getAndToken, _
        getStrNameToken("x1"), getLeConditionOperator, getStrNameToken("x2"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.toString(config)  = "and x1 <= x2"

    ' ==============================
    ' 19. 扫描器内: STR_NAME + [.] + STR_NAME + KEY_UNION
    '    测试: 解析 普通的列
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("x1"), getPointToken, getStrNameToken("x2"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.toString(config)  = "x1.x2"

    ' ==============================
    ' 20. 扫描器内: ( + STR_NAME + [.] + STR_NAME ) + KEY_UNION
    '    测试: 解析边界括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), getPointToken, getStrNameToken("x2"), _
        getRightParenthesisToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.toString(config) = "(x1.x2)"

    ' ==============================
    ' 21. 扫描器内: (( + STR_NAME + [.] + STR_NAME ) + KEY_UNION
    '    测试: 吸收多余的左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getPointToken, getStrNameToken("x2"), _
        getRightParenthesisToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.toString(config)  = "((x1.x2)"

    ' ==============================
    ' 22. 扫描器内: update
    '            + set + STR_NAME + [=] + STR_STR
    '            + where + STR_NAME [<=] + STR_NAME
    '            + union
    '    测试: 解析 udpate
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, _
        getSetToken, getStrNameToken("c1"), getEqConditionOperator, createStrToken("abc"), _
        getWhereToken, getStrNameToken("x1"), getLeConditionOperator, getStrNameToken("x2"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0)  = "update"
    Debug.Assert strArr.Item(1)  = "set"
    Debug.Assert strArr.Item(2)  = "    c1 = 'abc'"
    Debug.Assert strArr.Item(3)  = "where"
    Debug.Assert strArr.Item(4)  = "    x1 <= x2"

    ' ==============================
    ' 23. 扫描器内: set
    '            + STR_NAME + [=] + STR_STR
    '            + [,]
    '            + STR_NAME + [=] + STR_STR
    '    测试: 解析 set
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSetToken, _
        getStrNameToken("c1"), getEqConditionOperator, createStrToken("abc"), _
        getCommaToken, _
        getStrNameToken("c2"), getEqConditionOperator, createStrToken("xyz"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0)  = "set"
    Debug.Assert strArr.Item(1)  = "    c1 = 'abc',"
    Debug.Assert strArr.Item(2)  = "    c2 = 'xyz'"

    ' ==============================
    ' 24. 扫描器内: using
    '            + STR_NAME + t1
    '            + [,]
    '            + STR_NAME + t2
    '            + UNION
    '    测试基础结构
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getUsingToken, getStrNameToken("table1"), getStrNameToken("t1"), _
        getCommaToken, _
        getStrNameToken("table2"), getStrNameToken("t2"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_USING

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "using"
    Debug.Assert strArr.Item(1) = "    table1 t1,"
    Debug.Assert strArr.Item(2) = "    table2 t2"

    ' ==============================
    ' 25. 扫描器内: delete + 一个单行注释
    '             + low_priority
    '             + STR_NAME + [.] + [*]
    '             + [,]
    '             + STR_NAME + [.] + [*]
    '             + from table1 t1, table2 t2
    '             + where
    '                   + t1.a1 = 'abc'
    '                   + and t2.a2 = 'xyz'
    '    测试: 获取 delete 的基础结构
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getPointToken, getAsteriskToken, _
        getCommaToken, _
        getStrNameToken("x2"), getPointToken, getAsteriskToken, _
        _
        getFromToken, getStrNameToken("table1"), getStrNameToken("t1"), _
        getCommaToken, getStrNameToken("table2"), getStrNameToken("t2"), _
        _
        getWhereToken, _
        getStrNameToken("t1"), getPointToken, getStrNameToken("a1"), getEqConditionOperator, createStrToken("abc"), _
        getAndToken, _
        getStrNameToken("t2"), getPointToken, getStrNameToken("a2"), getEqConditionOperator, createStrToken("xyz"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 11
    Debug.Assert strArr.Item(0)  = "delete"
    Debug.Assert strArr.Item(1)  = "    --c1"
    Debug.Assert strArr.Item(2)  = "    low_priority"
    Debug.Assert strArr.Item(3)  = "    x1.*,"
    Debug.Assert strArr.Item(4)  = "    x2.*"
    Debug.Assert strArr.Item(5)  = "from"
    Debug.Assert strArr.Item(6)  = "    table1 t1,"
    Debug.Assert strArr.Item(7)  = "    table2 t2"
    Debug.Assert strArr.Item(8)  = "where"
    Debug.Assert strArr.Item(9)  = "    t1.a1 = 'abc'"
    Debug.Assert strArr.Item(10) = "    and t2.a2 = 'xyz'"

    ' ==============================
    ' 26. 扫描器内: into
    '          + STR_NAME
    '         + (
    '             STR_NAEM, STR_NAME
    '         + )
    '         + UNION
    '    测试扫描器内有 into 关键字 + 吸收关键字后面的注释 + 吸收表定义 + 列名
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getIntoToken, getStrNameToken("table1"), _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "into"
    Debug.Assert strArr.Item(1) = "    table1"
    Debug.Assert strArr.Item(2) = "    ("
    Debug.Assert strArr.Item(3) = "        x1,"
    Debug.Assert strArr.Item(4) = "        x2"
    Debug.Assert strArr.Item(5) = "    )"

    ' ==============================
    ' 27. 扫描器内: values + 一个同行注释
    '           + ( 'a1', 'a2' )
    '    测试扫描器内有 values 关键字 + 吸收一个 value
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getValuesToken, createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, createStrToken("a1"), getCommaToken, createStrToken("a2"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_VALUES
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "values --c1"
    Debug.Assert strArr.Item(1) = "    ("
    Debug.Assert strArr.Item(2) = "        'a1',"
    Debug.Assert strArr.Item(3) = "        'a2'"
    Debug.Assert strArr.Item(4) = "    )"

    ' ==============================
    ' 28. 扫描器内: on + DUPLICATE + KEY + UPDATE
    '            + c1 = 'abc',
    '            + c2 = 'xyz'
    '            + UNION
    '    测试: ON DUPLICATE 开头时，解析 `ON DUPLICATE KEY UPDATE` 作用域
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getKeyToken, getUpdateToken, _
        getStrNameToken("c1"), getEqConditionOperator, createStrToken("abc"), getCommaToken, _
        getStrNameToken("c2"), getEqConditionOperator, createStrToken("xyz"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_DUPLICATE
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext()
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "on duplicate key update"
    Debug.Assert strArr.Item(1) = "    c1 = 'abc',"
    Debug.Assert strArr.Item(2) = "    c2 = 'xyz'"

    ' ==============================
    ' 29. 扫描器内: insert + low_priority + ignore
    '            + into + STR_NAME(table1)
    '            + ( + c1, c2 + )
    '            + values ( 'v11', 'v12')
    '            + , ( 'v21', 'v22')
    '            + UNION
    '    测试: 获取 insert 的基础结构
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getInsertToken, getLowPriorityToken, _
        _
        getIntoToken, getStrNameToken("table1"), _
        _
        getLeftParenthesisToken, getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getRightParenthesisToken, _
        _
        getValuesToken, _
        getLeftParenthesisToken, createStrToken("v11"), getCommaToken, createStrToken("v12"), getRightParenthesisToken, _
        getCommaToken, _ 
        getLeftParenthesisToken, createStrToken("v21"), getCommaToken, createStrToken("v22"), getRightParenthesisToken, _
        _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnvFallback(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 17
    Debug.Assert strArr.Item(0)  = "insert"
    Debug.Assert strArr.Item(1)  = "    low_priority"
    Debug.Assert strArr.Item(2)  = "into"
    Debug.Assert strArr.Item(3)  = "    table1"
    Debug.Assert strArr.Item(4)  = "    ("
    Debug.Assert strArr.Item(5)  = "        c1,"
    Debug.Assert strArr.Item(6)  = "        c2"
    Debug.Assert strArr.Item(7)  = "    )"
    Debug.Assert strArr.Item(8) = "values"
    Debug.Assert strArr.Item(9) = "    ("
    Debug.Assert strArr.Item(10) = "        'v11',"
    Debug.Assert strArr.Item(11) = "        'v12'"
    Debug.Assert strArr.Item(12) = "    ),"
    Debug.Assert strArr.Item(13) = "    ("
    Debug.Assert strArr.Item(14) = "        'v21',"
    Debug.Assert strArr.Item(15) = "        'v22'"
    Debug.Assert strArr.Item(16) = "    )"
End Sub

' 测试: 解析 sql 的 token 并生成作用域---生成sql作用域的基础驱动--剥离左括号
Private Sub test_parseSqlEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内:
    '        ((
    '            select c1
    '            from t1
    '            union
    '            select c2
    '            from t2
    '            union all
    '            select c3
    '            from t3
    '        ))
    '        union
    '        select c4
    ' 测试: 有边界括号 + 边界括号内部的 UNION 可以被吸收 + needParseUnion=false
    '       + 边界括号后的 UNION 不会被处理
    ' ==============================
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)

    '        ((
    addElemToArray meanTokens, getLeftParenthesisToken, getLeftParenthesisToken

    '            select c1
    '            from t1
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("c1"), _
        getFromToken, getStrNameToken("t1")

    '            union
    addElemToArray meanTokens, getUnionToken

    '            select c2
    '            from t2
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("c2"), _
        getFromToken, getStrNameToken("t2")

    '            union all
    addElemToArray meanTokens, getUnionToken, getAllToken

    '            select c3
    '            from t3
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("c3"), _
        getFromToken, getStrNameToken("t3")

    '        ))
    addElemToArray meanTokens, getRightParenthesisToken, getRightParenthesisToken

    '        union
    '        select c4
    addElemToArray meanTokens, _
        getUnionToken, _
        getSelectToken, getStrNameToken("c4")

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnv(scanner, False)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 16
    Debug.Assert strArr.Item(0)  = "(("
    Debug.Assert strArr.Item(1)  = "    select"
    Debug.Assert strArr.Item(2)  = "        c1"
    Debug.Assert strArr.Item(3)  = "    from"
    Debug.Assert strArr.Item(4)  = "        t1"
    Debug.Assert strArr.Item(5)  = "    union"
    Debug.Assert strArr.Item(6)  = "    select"
    Debug.Assert strArr.Item(7)  = "        c2"
    Debug.Assert strArr.Item(8)  = "    from"
    Debug.Assert strArr.Item(9)  = "        t2"
    Debug.Assert strArr.Item(10) = "    union all"
    Debug.Assert strArr.Item(11) = "    select"
    Debug.Assert strArr.Item(12) = "        c3"
    Debug.Assert strArr.Item(13) = "    from"
    Debug.Assert strArr.Item(14) = "        t3"
    Debug.Assert strArr.Item(15) = "))"

    ' ==============================
    ' 2. 扫描器内:
    '        ((
    '            select c1
    '            from t1
    '            union
    '            select c2
    '            from t2
    '            union all
    '            select c3
    '            from t3
    '        ))
    '        union --c1
    '        select c4
    ' 测试: 有边界括号 + 边界括号内部的 UNION 可以被吸收 + needParseUnion=true
    '       + 边界括号后的 UNION 会被处理
    ' ==============================
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(30)

    '        ((
    addElemToArray meanTokens, getLeftParenthesisToken, getLeftParenthesisToken

    '            select c1
    '            from t1
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("c1"), _
        getFromToken, getStrNameToken("t1")

    '            union
    addElemToArray meanTokens, getUnionToken

    '            select c2
    '            from t2
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("c2"), _
        getFromToken, getStrNameToken("t2")

    '            union all
    addElemToArray meanTokens, getUnionToken, getAllToken

    '            select c3
    '            from t3
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("c3"), _
        getFromToken, getStrNameToken("t3")

    '        ))
    addElemToArray meanTokens, getRightParenthesisToken, getRightParenthesisToken

    '        union --c1
    '        select c4
    addElemToArray meanTokens, _
        getUnionToken, createShareLineCommentToken("--c1"), _
        getSelectToken, getStrNameToken("c4")

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnv(scanner, True)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 19
    Debug.Assert strArr.Item(0)  = "(("
    Debug.Assert strArr.Item(1)  = "    select"
    Debug.Assert strArr.Item(2)  = "        c1"
    Debug.Assert strArr.Item(3)  = "    from"
    Debug.Assert strArr.Item(4)  = "        t1"
    Debug.Assert strArr.Item(5)  = "    union"
    Debug.Assert strArr.Item(6)  = "    select"
    Debug.Assert strArr.Item(7)  = "        c2"
    Debug.Assert strArr.Item(8)  = "    from"
    Debug.Assert strArr.Item(9)  = "        t2"
    Debug.Assert strArr.Item(10) = "    union all"
    Debug.Assert strArr.Item(11) = "    select"
    Debug.Assert strArr.Item(12) = "        c3"
    Debug.Assert strArr.Item(13) = "    from"
    Debug.Assert strArr.Item(14) = "        t3"
    Debug.Assert strArr.Item(15) = "))"
    Debug.Assert strArr.Item(16) = "union --c1"
    Debug.Assert strArr.Item(17) = "select"
    Debug.Assert strArr.Item(18) = "    c4"

    ' ==============================
    ' 3. 扫描器内:
    '        ((
    '        union join
    ' 测试: 查询无法被解析，左括号被强制吸收 + 再向后强制吸收一个元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, getLeftParenthesisToken, _
        getUnionToken, getJoinToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnv(scanner, True)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_JOIN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "("
    Debug.Assert strArr.Item(1) = "("
    Debug.Assert strArr.Item(2) = "union"

    ' ==============================
    ' 4. 扫描器内:
    '       ( + 一个同行注释 + ( + (
    '          select + STR_NAME
    ' 测试: 查询被解析 + 吸收多余的左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlEnv(scanner, True)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    (("
    Debug.Assert strArr.Item(2) = "        select"
    Debug.Assert strArr.Item(3) = "            x1"

End Sub

' 测试: 解析并吸收union开始的作用域
Private Sub test_parseFromUnionEnv()

    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack
    Dim startEnv As SqlFormatterContainerEnv

    ' ==============================
    ' 1. startEnv 内容
    '            select
    '     扫描器内:
    '            union
    '            select c2
    '            from t2
    '            )
    '     无左括号栈
    ' 测试: 测试基础功能 + 在右括号处可以停止
    ' ==============================
    ' 1. 创建 startEnv
    Set startEnv = New SqlFormatterContainerEnv
    startEnv.ISqlFormatterElem_addComponent getSelectToken
    startEnv.setType SqlTokenType.KEY_SELECT

    ' 2. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken, _
        getSelectToken, getStrNameToken("c2"), _
        getFromToken, getStrNameToken("t2"), _
        getRightParenthesisToken)

    ' 3. 执行测试方法
    Set elem = analyzer.parseFromUnionEnv(scanner, Nothing, startEnv)
    ' 4. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "union"
    Debug.Assert strArr.Item(2)  = "select"
    Debug.Assert strArr.Item(3)  = "    c2"
    Debug.Assert strArr.Item(4)  = "from"
    Debug.Assert strArr.Item(5)  = "    t2"

    ' ==============================
    ' 2. startEnv 内容
    '            select
    '     扫描器内:
    '            union
    '            select c2
    '            from t2
    '            )
    '     左括号栈: 一个左括号
    ' 测试: 测试基础功能 + 吸收边界括号
    ' ==============================
    ' 1. 创建 startEnv
    Set startEnv = New SqlFormatterContainerEnv
    startEnv.ISqlFormatterElem_addComponent getSelectToken
    startEnv.setType SqlTokenType.KEY_SELECT

    ' 2. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken, _
        getSelectToken, getStrNameToken("c2"), _
        getFromToken, getStrNameToken("t2"), _
        getRightParenthesisToken)

    ' 3. 创建左括号栈
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken

    ' 4. 执行测试方法
    Set elem = analyzer.parseFromUnionEnv(scanner, stack, startEnv)
    ' 5. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 8
    Debug.Assert strArr.Item(0)  = "("
    Debug.Assert strArr.Item(1)  = "    select"
    Debug.Assert strArr.Item(2)  = "    union"
    Debug.Assert strArr.Item(3)  = "    select"
    Debug.Assert strArr.Item(4)  = "        c2"
    Debug.Assert strArr.Item(5)  = "    from"
    Debug.Assert strArr.Item(6)  = "        t2"
    Debug.Assert strArr.Item(7)  = ")"

    ' ==============================
    ' 3. startEnv 内容
    '            select
    '     扫描器内:
    '            union
    '            join
    '     无左括号栈
    ' 测试: 测试基础功能 + union 后无法解析处作用域
    ' ==============================
    ' 1. 创建 startEnv
    Set startEnv = New SqlFormatterContainerEnv
    startEnv.ISqlFormatterElem_addComponent getSelectToken
    startEnv.setType SqlTokenType.KEY_SELECT

    ' 2. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken, getJoinToken)

    ' 3. 执行测试方法
    Set elem = analyzer.parseFromUnionEnv(scanner, stack, startEnv)
    ' 4. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UNION
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_JOIN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "union"

End Sub

' 测试: 解析sql并生成基础作用域--剥离with
Private Sub test_parseBaseEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 扫描器内:
    '            with w1 as (
    '               select t1
    '               from table1
    '            ),
    '            w2 as (
    '               select t2
    '               from table2
    '            )
    '            --c1
    '            (
    '                with w3 as (
    '                   select t3
    '                   from table
    '                ),
    '                w4 as (
    '                   select t4
    '                   from table4
    '                )
    '            ) --c2
    '            select t5
    '            from table5
    '         ))
    ' 测试: 左括号栈 + 右边界括号吸收 + 多个 with 作用域 + with 和 select 进行合并
    '      + 作用域间的注释可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Dim meanTokens As IObjArrayRef
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(100)

    '            with w1 as (
    '               select t1
    '               from table1
    '            ),
    addElemToArray meanTokens, _
        getWithToken, getStrNameToken("w1"), getAsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("t1"), _
        getFromToken, getStrNameToken("table1"), _
        getRightParenthesisToken, getCommaToken

    '            w2 as (
    '               select t2
    '               from table2
    '            )
    addElemToArray meanTokens, _
        getStrNameToken("w2"), getAsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("t2"), _
        getFromToken, getStrNameToken("table2"), _
        getRightParenthesisToken

    '            --c1
    addElemToArray meanTokens, createSingleLineCommentToken("--c1")

    '            (
    addElemToArray meanTokens, getLeftParenthesisToken

    '                with w3 as (
    '                   select t3
    '                   from table
    '                ),
    addElemToArray meanTokens, _
        getWithToken, getStrNameToken("w3"), getAsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("t3"), _
        getFromToken, getStrNameToken("table3"), _
        getRightParenthesisToken, getCommaToken

    '                w4 as (
    '                   select t4
    '                   from table4
    '                )
    addElemToArray meanTokens, _
        getStrNameToken("w4"), getAsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("t4"), _
        getFromToken, getStrNameToken("table4"), _
        getRightParenthesisToken

    '            ) --c2
    addElemToArray meanTokens, getRightParenthesisToken, createShareLineCommentToken("--c2")

    '            select t5
    '            from table5
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("t5"), _
        getFromToken, getStrNameToken("table5")

    '         ))
    addElemToArray meanTokens, getRightParenthesisToken, getRightParenthesisToken

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 初始化左括号栈
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push getLeftParenthesisToken

    ' 3. 执行测试方法
    Set elem = analyzer.parseBaseEnv(scanner, stack)
    ' 4. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 33
    Debug.Assert strArr.Item(0)  = "(("
    Debug.Assert strArr.Item(1)  = "    with w1 as ("
    Debug.Assert strArr.Item(2)  = "        select"
    Debug.Assert strArr.Item(3)  = "            t1"
    Debug.Assert strArr.Item(4)  = "        from"
    Debug.Assert strArr.Item(5)  = "            table1"
    Debug.Assert strArr.Item(6)  = "    ),"
    Debug.Assert strArr.Item(7)  = "    w2 as ("
    Debug.Assert strArr.Item(8)  = "        select"
    Debug.Assert strArr.Item(9)  = "            t2"
    Debug.Assert strArr.Item(10) = "        from"
    Debug.Assert strArr.Item(11) = "            table2"
    Debug.Assert strArr.Item(12) = "    )"
    Debug.Assert strArr.Item(13) = "    --c1"
    Debug.Assert strArr.Item(14) = "    ("
    Debug.Assert strArr.Item(15) = "        with w3 as ("
    Debug.Assert strArr.Item(16) = "            select"
    Debug.Assert strArr.Item(17) = "                t3"
    Debug.Assert strArr.Item(18) = "            from"
    Debug.Assert strArr.Item(19) = "                table3"
    Debug.Assert strArr.Item(20) = "        ),"
    Debug.Assert strArr.Item(21) = "        w4 as ("
    Debug.Assert strArr.Item(22) = "            select"
    Debug.Assert strArr.Item(23) = "                t4"
    Debug.Assert strArr.Item(24) = "            from"
    Debug.Assert strArr.Item(25) = "                table4"
    Debug.Assert strArr.Item(26) = "        )"
    Debug.Assert strArr.Item(27) = "    ) --c2"
    Debug.Assert strArr.Item(28) = "    select"
    Debug.Assert strArr.Item(29) = "        t5"
    Debug.Assert strArr.Item(30) = "    from"
    Debug.Assert strArr.Item(31) = "        table5"
    Debug.Assert strArr.Item(32) = "))"

End Sub

' 测试: 解析完整的 with 作用域---外层逻辑 -- 进行括号配对
Private Sub test_parseWithEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 扫描器内: UNION
    '    没有左括号栈 + 测试开头不是 with
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 2. 扫描器内: with
    '    没有左括号栈 + 测试开头只有 with
    ' ==============================
    ' 1. 创建扫描器
    Set token = getWithToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 3. 扫描器内: with + UNION
    '    没有左括号栈 + 测试开头 with + 遇到 非withTable 内容终止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "with"

    ' ==============================
    ' 4. 扫描器内: with + RECURSIVE + 一个注释 + RECURSIVE + 一个注释
    '    没有左括号栈 + 测试开头 with + 吸收 RECURSIVE
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, _
        getRecursiveToken, createShareLineCommentToken("--c1"), _
        getRecursiveToken, createShareLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "with recursive --c1"
    Debug.Assert strArr.Item(1) = "recursive --c2"

    ' ==============================
    ' 5. 扫描器内: with + RECURSIVE + 一个注释 + RECURSIVE + 一个注释
    '              + UNION
    '    没有左括号栈 + 测试开头 with + 吸收 RECURSIVE + 遇到 非withTable 内容终止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, _
        getRecursiveToken, createShareLineCommentToken("--c1"), _
        getRecursiveToken, createShareLineCommentToken("--c2"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "with recursive --c1"
    Debug.Assert strArr.Item(1) = "recursive --c2"

    ' ==============================
    ' 5. 扫描器内: with + RECURSIVE
    '             + STR_FUN
    '             + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select + STR_NAME
    '                  + from + STR_NAME
    '             + )
    '    没有左括号栈 + 测试开头 with + 吸收 RECURSIVE + 一个表定义
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, getRecursiveToken, _
        createFuncNameToken("t1"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("a1"), _
        getFromToken, getStrNameToken("table1"), _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "with recursive t1 (x1, x2) as ("
    Debug.Assert strArr.Item(1) = "    select"
    Debug.Assert strArr.Item(2) = "        a1"
    Debug.Assert strArr.Item(3) = "    from"
    Debug.Assert strArr.Item(4) = "        table1"
    Debug.Assert strArr.Item(5) = ")"

    ' ==============================
    ' 6. 扫描器内: with + RECURSIVE
    '             + STR_FUN
    '             + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select + STR_NAME
    '                  + from + STR_NAME
    '             + )
    '             + UNION
    '    没有左括号栈 + 测试开头 with + 吸收 RECURSIVE + 一个表定义 + 遇到 非withTable 内容终止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, getRecursiveToken, _
        createFuncNameToken("t1"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("a1"), _
        getFromToken, getStrNameToken("table1"), _
        getRightParenthesisToken, _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "with recursive t1 (x1, x2) as ("
    Debug.Assert strArr.Item(1) = "    select"
    Debug.Assert strArr.Item(2) = "        a1"
    Debug.Assert strArr.Item(3) = "    from"
    Debug.Assert strArr.Item(4) = "        table1"
    Debug.Assert strArr.Item(5) = ")"

    ' ==============================
    ' 7. 扫描器内: with + RECURSIVE
    '             + STR_FUN
    '             + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select + STR_NAME
    '             + )
    '             + [,] + 一个注释
    '    没有左括号栈 + 测试开头 with + 吸收 RECURSIVE + 一个表定义 + 吸收逗号和注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, getRecursiveToken, _
        createFuncNameToken("t1"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("a1"), _
        getRightParenthesisToken, _
        _
        getCommaToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "with recursive t1 (x1, x2) as ("
    Debug.Assert strArr.Item(1) = "    select"
    Debug.Assert strArr.Item(2) = "        a1"
    Debug.Assert strArr.Item(3) = "), --c1"

    ' ==============================
    ' 8. 扫描器内: with + RECURSIVE
    '     (第一个) + STR_FUN
    '             + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select + STR_NAME
    '             + )
    '             + [,] + 一个注释
    '    (第二个) + STR_FUN
    '             + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select + STR_NAME
    '             + )
    '             + [,] + 一个注释
    '    (第三个) + STR_FUN
    '             + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select + STR_NAME
    '             + )
    '    没有左括号栈 + 测试开头 with + 吸收 RECURSIVE + 一个表定义 + 吸收逗号和注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, getRecursiveToken, _
        _
        createFuncNameToken("t1"), _
        getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        getAsToken, _
        getLeftParenthesisToken, getSelectToken, getStrNameToken("a1"), getRightParenthesisToken, _
        _
        getCommaToken, createShareLineCommentToken("--c1"), _
        _
        createFuncNameToken("t2"), _
        getLeftParenthesisToken, getStrNameToken("y1"), getCommaToken, getStrNameToken("y2"), getRightParenthesisToken, _
        getAsToken, getLeftParenthesisToken, getSelectToken, getStrNameToken("b1"), getRightParenthesisToken, _
        _
        getCommaToken, createShareLineCommentToken("--c2"), _
        _
        createFuncNameToken("t3"), _
        getLeftParenthesisToken, getStrNameToken("z1"), getCommaToken, getStrNameToken("z2"), getRightParenthesisToken, _
        getAsToken, getLeftParenthesisToken, getSelectToken, getStrNameToken("c1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 12
    Debug.Assert strArr.Item(0)  = "with recursive t1 (x1, x2) as ("
    Debug.Assert strArr.Item(1)  = "    select"
    Debug.Assert strArr.Item(2)  = "        a1"
    Debug.Assert strArr.Item(3)  = "), --c1"
    Debug.Assert strArr.Item(4)  = "t2 (y1, y2) as ("
    Debug.Assert strArr.Item(5)  = "    select"
    Debug.Assert strArr.Item(6)  = "        b1"
    Debug.Assert strArr.Item(7)  = "), --c2"
    Debug.Assert strArr.Item(8)  = "t3 (z1, z2) as ("
    Debug.Assert strArr.Item(9)  = "    select"
    Debug.Assert strArr.Item(10) = "        c1"
    Debug.Assert strArr.Item(11) = ")"

    ' ==============================
    ' 9. 扫描器内: with
    '             + STR_FUN + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + with
    '                       + STR_FUN + ( + STR_NAME + [,] + STR_NAME + )
    '                       + As + (
    '                       + select + STR_NAME
    '                       + )
    '                  + select + STR_NAME
    '                  + from
    '                      + (
    '                      +     select + STR_NAME
    '                      +     from + STR_NAME
    '                      + ) + as + STR_NAME
    '             + )
    '    没有左括号栈 + with 的表定义中还有 with + select 中的表定义是子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getWithToken, _
        _
        createFuncNameToken("t1"), getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        _
        getWithToken, _
        _
        createFuncNameToken("tx"), getLeftParenthesisToken, getStrNameToken("y1"), getCommaToken, getStrNameToken("y2"), getRightParenthesisToken, _
        _
        getAsToken, getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("w1"), _
        _
        getRightParenthesisToken, _
        _
        getSelectToken, getStrNameToken("c1"), _
        getFromToken, _
        getLeftParenthesisToken, getSelectToken, getStrNameToken("k1"), getFromToken, getStrNameToken("k2"), getRightParenthesisToken, _
        getAsToken, getStrNameToken("nk"), _
        _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 15
    Debug.Assert strArr.Item(0)  = "with t1 (x1, x2) as ("
    Debug.Assert strArr.Item(1)  = "    with tx (y1, y2) as ("
    Debug.Assert strArr.Item(2)  = "        select"
    Debug.Assert strArr.Item(3)  = "            w1"
    Debug.Assert strArr.Item(4)  = "    )"
    Debug.Assert strArr.Item(5)  = "    select"
    Debug.Assert strArr.Item(6)  = "        c1"
    Debug.Assert strArr.Item(7)  = "    from"
    Debug.Assert strArr.Item(8)  = "        ("
    Debug.Assert strArr.Item(9)  = "            select"
    Debug.Assert strArr.Item(10) = "                k1"
    Debug.Assert strArr.Item(11) = "            from"
    Debug.Assert strArr.Item(12) = "                k2"
    Debug.Assert strArr.Item(13) = "        ) as nk"
    Debug.Assert strArr.Item(14) = ")"

    ' ==============================
    ' 10. 扫描器内: with + RECURSIVE
    '             + STR_FUN
    '             + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select + STR_NAME
    '                  + from + STR_NAME
    '             + )
    '             + ) + 一个注释 + )
    '    测试: 左括号栈可以被吸收 + 测试开头 with + 吸收 RECURSIVE + 一个表定义
    '          左括号栈内=一个左括号 + 一个左括号 + 一个注释 + 一个左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, getRecursiveToken, _
        createFuncNameToken("t1"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("a1"), _
        getFromToken, getStrNameToken("table1"), _
        getRightParenthesisToken, _
        getRightParenthesisToken, createShareLineCommentToken("--c2"), getRightParenthesisToken)
    ' 2. 创建左括号栈
    Set stack = New ObjStack
    stack.push getLeftParenthesisToken
    stack.push getLeftParenthesisToken
    stack.push createShareLineCommentToken("--c1")
    stack.push getLeftParenthesisToken

    Debug.Assert stack.Count = 4

    ' 3. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, stack)

    ' 4. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False

    Debug.Assert stack.Count = 1
    Set token = stack.pop
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 10
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    ("
    Debug.Assert strArr.Item(2) = "        with recursive t1 (x1, x2) as ("
    Debug.Assert strArr.Item(3) = "            select"
    Debug.Assert strArr.Item(4) = "                a1"
    Debug.Assert strArr.Item(5) = "            from"
    Debug.Assert strArr.Item(6) = "                table1"
    Debug.Assert strArr.Item(7) = "        )"
    Debug.Assert strArr.Item(8) = "    ) --c2"
    Debug.Assert strArr.Item(9) = ")"

    ' ==============================
    ' 11. 扫描器内: with + 一个同行注释
    '             + 一个单行注释 + RECURSIVE
    '             + STR_FUN
    '             + ( + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select + STR_NAME
    '             + )
    '    测试开头 with + 后面的同行注释可以被吸收，并且连接字符串时和 with 在同一行
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, createShareLineCommentToken("--c1"), _
        createSingleLineCommentToken("--c2"), getRecursiveToken, _
        createFuncNameToken("t1"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("a1"), _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "with --c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "recursive t1 (x1, x2) as ("
    Debug.Assert strArr.Item(3) = "    select"
    Debug.Assert strArr.Item(4) = "        a1"
    Debug.Assert strArr.Item(5) = ")"

    ' ==============================
    ' 12. 扫描器内: with + RECURSIVE
    '    (01)         + 一个同行注释1 + 一个单行注释2
    '    (02) (第一个) + STR_FUN As ( + select + STR_NAME + ) + 一个同行注释3
    '    (03)         + 一个单行注释4
    '    (04)         + [,] + 一个同行注释5
    '    (05)         + 一个单行注释6
    '    (06)(第二个) + STR_FUN As ( + select + STR_NAME + ) + 一个同行注释7
    '    (07)         + 一个单行注释8
    '    (08)         + [,] + 一个同行注释9
    '    (09)         + 一个单行注释10
    '    (10)(第三个) + STR_FUN As ( + select + STR_NAME + ) + 一个同行注释11
    '    (11)         + 一个单行注释12
    '    (12)         + union
    '    测试: 吸收各个部分的注释 + 下一个部分的注释不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWithToken, getRecursiveToken, _
        _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        _
        createFuncNameToken("t1"), getAsToken, getLeftParenthesisToken, getSelectToken, getStrNameToken("a1"), getRightParenthesisToken, createShareLineCommentToken("--c3"), _
        _
        createSingleLineCommentToken("--c4"), _
        _
        getCommaToken, createShareLineCommentToken("--c5"), _
        _
        createSingleLineCommentToken("--c6"), _
        _
        createFuncNameToken("t2"), getAsToken, getLeftParenthesisToken, getSelectToken, getStrNameToken("a2"), getRightParenthesisToken, createShareLineCommentToken("--c7"), _
        _
        createSingleLineCommentToken("--c8"), _
        _
        getCommaToken, createShareLineCommentToken("--c9"), _
        _
        createSingleLineCommentToken("--c10"), _
        _
        createFuncNameToken("t3"), getAsToken, getLeftParenthesisToken, getSelectToken, getStrNameToken("a3"), getRightParenthesisToken, createShareLineCommentToken("--c11"), _
        _
        createSingleLineCommentToken("--c12"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseWithEnv(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c12"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 20
    Debug.Assert strArr.Item(0)  = "with recursive --c1"
    Debug.Assert strArr.Item(1)  = "--c2"
    Debug.Assert strArr.Item(2)  = "t1 as ("
    Debug.Assert strArr.Item(3)  = "    select"
    Debug.Assert strArr.Item(4)  = "        a1"
    Debug.Assert strArr.Item(5)  = ") --c3"
    Debug.Assert strArr.Item(6)  = "--c4"
    Debug.Assert strArr.Item(7)  = ", --c5"
    Debug.Assert strArr.Item(8)  = "--c6"
    Debug.Assert strArr.Item(9)  = "t2 as ("
    Debug.Assert strArr.Item(10) = "    select"
    Debug.Assert strArr.Item(11) = "        a2"
    Debug.Assert strArr.Item(12) = ") --c7"
    Debug.Assert strArr.Item(13) = "--c8"
    Debug.Assert strArr.Item(14) = ", --c9"
    Debug.Assert strArr.Item(15) = "--c10"
    Debug.Assert strArr.Item(16) = "t3 as ("
    Debug.Assert strArr.Item(17) = "    select"
    Debug.Assert strArr.Item(18) = "        a3"
    Debug.Assert strArr.Item(19) = ") --c11"

End Sub

' 测试: 解析一个with表作用域
Private Sub test_parseWithTableEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: STR_NAME
    '    测试只有 cte_name + 开头的 cte_name 类型是 STR_NAME
    ' ==============================
    Set token = getStrNameToken("t1")
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: STR_FUN
    '    测试只有 cte_name + 开头的 cte_name 类型是 STR_FUN + 这个元素的类型会被修改为 STR_NAME
    ' ==============================
    Set token = createFuncNameToken("t1")
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert token.tokenType = SqlTokenType.STR_NAME
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 3. 扫描器内: UNION
    '    测试开头的 cte_name 无法被解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 4. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释
    '    测试解析 cte_name + 吸收 cte_name 后面的注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "t1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"

    ' ==============================
    ' 5. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释 + UNION
    '    测试解析 cte_name + 吸收 cte_name 后面的注释 + 遇到非as元素时停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "t1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"

    ' ==============================
    ' 6. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释 + As
    '    测试解析 cte_name + 吸收 cte_name 后面的注释 + 没有 col_name + 吸收 As
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getAsToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "t1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "as"

    ' ==============================
    ' 7. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释 + As + UNION
    '    测试解析 cte_name + 吸收 cte_name 后面的注释 + 没有 col_name + 吸收 As
    '             + As 后面的非子查询无法解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getAsToken, getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "t1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "as"

    ' ==============================
    ' 8. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释
    '             ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '    测试解析 cte_name + 吸收 cte_name 后面的注释 + 吸收 col_name
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getCommaToken, getStrNameToken("x3"), _
        getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "t1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "(x1, x2, x3)"

    ' ==============================
    ' 9. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '             + UNION
    '    测试解析 cte_name + 吸收 cte_name 后面的注释 + 吸收 col_name + 非As元素被终止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getCommaToken, getStrNameToken("x3"), _
        getRightParenthesisToken, _
        _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "t1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "(x1, x2, x3)"

    ' ==============================
    ' 10. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '             + As
    '    测试解析 cte_name + 吸收 cte_name 后面的注释 + 吸收 col_name + 吸收As
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getCommaToken, getStrNameToken("x3"), _
        getRightParenthesisToken, _
        _
        getAsToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "t1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "(x1, x2, x3) as"

    ' ==============================
    ' 11. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '             + As
    '             + (
    '                  + select +STR_NAME
    '                  + from + STR_NAME
    '             + )
    '    测试解析 cte_name + 吸收 cte_name 后面的注释 + 吸收 col_name + 吸收As
    '             + 吸收一个 select 子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getCommaToken, getStrNameToken("x3"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a1"), _
        _
        getFromToken, getStrNameToken("table1"), _
        _
        getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 8
    Debug.Assert strArr.Item(0) = "t1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = "(x1, x2, x3) as ("
    Debug.Assert strArr.Item(3) = "    select"
    Debug.Assert strArr.Item(4) = "        a1"
    Debug.Assert strArr.Item(5) = "    from"
    Debug.Assert strArr.Item(6) = "        table1"
    Debug.Assert strArr.Item(7) = ")"

    ' ==============================
    ' 12. 扫描器内: STR_FUN + 一个同行注释 + 一个单行注释
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '             + As
    '                 + (
    '                      + select + STR_NAME
    '                      + from + STR_NAME
    '             + UNION
    '                 + (
    '                      + select +STR_NAME
    '                      + from + STR_NAME
    '             + )
    '    测试解析 cte_name + 吸收 cte_name 后面的注释 + 吸收 col_name + 吸收As
    '             + 吸收一个 select 子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getCommaToken, getStrNameToken("x3"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a1"), _
        getFromToken, getStrNameToken("table1"), _
        _
        getUnionToken, _
        _
        getSelectToken, getStrNameToken("a2"), _
        getFromToken, getStrNameToken("table2"), _
        _
        getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 13
    Debug.Assert strArr.Item(0)  = "t1 --c1"
    Debug.Assert strArr.Item(1)  = "--c2"
    Debug.Assert strArr.Item(2)  = "(x1, x2, x3) as ("
    Debug.Assert strArr.Item(3)  = "    select"
    Debug.Assert strArr.Item(4)  = "        a1"
    Debug.Assert strArr.Item(5)  = "    from"
    Debug.Assert strArr.Item(6)  = "        table1"
    Debug.Assert strArr.Item(7)  = "    union"
    Debug.Assert strArr.Item(8)  = "    select"
    Debug.Assert strArr.Item(9)  = "        a2"
    Debug.Assert strArr.Item(10) = "    from"
    Debug.Assert strArr.Item(11) = "        table2"
    Debug.Assert strArr.Item(12) = ")"

    ' ==============================
    ' 13. 扫描器内: STR_FUN
    '             + As
    '                 + (
    '                      + select + STR_NAME
    '                      + from + STR_NAME
    '             + UNION
    '                 + (
    '                      + select + STR_NAME
    '                      + from + STR_NAME
    '                 + )
    '    测试: 无 with 表的列名部分
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("t1"), _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a1"), _
        getFromToken, getStrNameToken("table1"), _
        _
        getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterWithTableEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0)  = "t1 as ("
    Debug.Assert strArr.Item(1)  = "    select"
    Debug.Assert strArr.Item(2)  = "        a1"
    Debug.Assert strArr.Item(3)  = "    from"
    Debug.Assert strArr.Item(4)  = "        table1"
    Debug.Assert strArr.Item(5) = ")"

End Sub

' 测试: 解析with表作用域中的列名
Private Sub test_parseWithTableColNames()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: UNION
    '    测试开头不是左括号
    ' ==============================
    Set token = getUnionToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Debug.Assert scanner.getNext Is token

    ' ==============================
    ' 2. 扫描器内: (
    '    测试只有开头的左括号
    ' ==============================
    Set token = getLeftParenthesisToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 3. 扫描器内: ( + UNION
    '    测试只有开头的左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "("

    ' ==============================
    ' 4. 扫描器内: ( + )
    '    测试开头有多个左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, getLeftParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "(("

    ' ==============================
    ' 5. 扫描器内: ( + ( + 一个同行注释 + 一个单行注释
    '    测试开头有多个左括号 + 注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, getLeftParenthesisToken, _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "(( --c1"
    Debug.Assert strArr.Item(1) = "    --c2"

    ' ==============================
    ' 6. 扫描器内: ( + 一个同行注释 + STR_NAME
    '    测试只有一个列名
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("x1"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    x1"

    ' ==============================
    ' 7. 扫描器内: ( + 一个同行注释 + STR_NAME
    '            + [,] + STR_NAME + UNION
    '    测试有多个列名 + 遇到 ) 以外的元素能够停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    x1,"
    Debug.Assert strArr.Item(2) = "    x2"

    ' ==============================
    ' 8. 扫描器内: ( + 一个同行注释
    '            + STR_NAME
    '            + [,] + 一个同行注释 + STR_NAME + 一个同行注释 + UNION
    '    测试有多个列名 + 列名中间有注释 + 遇到 ) 以外的元素能够停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, createShareLineCommentToken("--c1"), _
        getStrNameToken("x1"), _
        getCommaToken, createShareLineCommentToken("--c2"), _
        getStrNameToken("x2"), createShareLineCommentToken("--c3"), _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "( --c1"
    Debug.Assert strArr.Item(1) = "    x1, --c2"
    Debug.Assert strArr.Item(2) = "    x2 --c3"

    ' ==============================
    ' 9. 扫描器内: (
    '            + [,] + 一个同行注释 + STR_NAME + 一个同行注释 + UNION
    '    测试以左括号和逗号开头 + 遇到 ) 以外的元素能够停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, _
        getCommaToken, getStrNameToken("x1"), createShareLineCommentToken("--c1"), _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "("
    Debug.Assert strArr.Item(1) = "    ,"
    Debug.Assert strArr.Item(2) = "    x1 --c1"

    ' ==============================
    ' 10. 扫描器内: ( + ( + 一个同行注释 + STR_NAME
    '            + [,] + 一个同行注释 + STR_NAME + 一个同行注释 + ) + ) + )
    '    测试有多个列名 + 列名中间有注释 + 遇到 ) 停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, getLeftParenthesisToken, createShareLineCommentToken("--c1"), _
        getStrNameToken("x1"), createShareLineCommentToken("--c2"), _
        getCommaToken, getStrNameToken("x2"), createShareLineCommentToken("--c3"), _
        getRightParenthesisToken, getRightParenthesisToken, getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "(( --c1"
    Debug.Assert strArr.Item(1) = "    x1 --c2"
    Debug.Assert strArr.Item(2) = "    ,"
    Debug.Assert strArr.Item(3) = "    x2 --c3"
    Debug.Assert strArr.Item(4) = "))"

    ' ==============================
    ' 11. 扫描器内: ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '    生成单行结构
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getCommaToken, getStrNameToken("x3"), _
        getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseWithTableColNames(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "(x1, x2, x3)"

End Sub

' 测试: 解析 select sql片段，并生成作用域
' 基础结构测试
Private Sub test_parseSelect_base()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内:
    '    select + 一个注释
    '         + distinct
    '         + STR_NAME + [,]
    '         + STR_NAME
    '    + from
    '         + (第一个表定义) 一个表定义(tableName) + 一个注释 + partition
    '               + as + STR_NAME + 一个注释
    '               + 一个 index_hint_list
    '         + [,]
    '         + (第二个表定义) 一个表定义(tableName) + STR_NAME(别名)
    '    + left + join
    '        + 一个表定义(tableName) + as + STR_NAME(别名)
    '    + on
    '        +STR_NAME + <= + STR_NAME
    '        +and + STR_NAME + <= + STR_NAME
    '    + where
    '        + STR_NAME + <= + STR_NAME
    '        + and + STR_NAME + <= + STR_NAME
    '    + groupby
    '        + STR_NAME + ASC
    '        + [,] + STR_NAME + DESC + 一个注释
    '        + with + rollup
    '    + order by
    '        + STR_NAME + ASC
    '        + [,] + STR_NAME + DESC
    '    + having
    '        + STR_NAME + <= + STR_NAME
    '        + and + STR_NAME + <= + STR_NAME
    '    + Limit + 一个数字 + offset + 一个数字 (LIMIT row_count OFFSET offset)
    ' 基础结构测试
    ' ==============================
    ' 1. 创建扫描器
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(100)
    '    添加筛选项
    addElemToArray meanTokens, _
        getSelectToken, createShareLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2")
    '    添加 from
    addElemToArray meanTokens, _
        getFromToken, _
        _
        getStrNameToken("tableX"), createShareLineCommentToken("--c1"), _
        getPartitionToken, getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        _
        getAsToken, getStrNameToken("t1"), createShareLineCommentToken("--c2"), _
        _
        getUseToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        getCommaToken, _
        getForceToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x3"), getCommaToken, getStrNameToken("x4"), getRightParenthesisToken, _
        getCommaToken, getStrNameToken("table2"), getStrNameToken("t2")
    '    添加第一个 left
    addElemToArray meanTokens, _
        getLeftToken, getJoinToken, _
        getStrNameToken("table3"), getAsToken, getStrNameToken("t3"), _
        getOnToken, getStrNameToken("cx1"), getLeConditionOperator, getStrNameToken("cx2"), _
        getAndToken, getStrNameToken("cx3"), getLeConditionOperator, getStrNameToken("cx4")
    '    添加第二个 left
    addElemToArray meanTokens, _
        getLeftToken, getJoinToken, _
        getStrNameToken("table4"), getAsToken, getStrNameToken("t4"), _
        getOnToken, getStrNameToken("cy1"), getLeConditionOperator, getStrNameToken("cy2"), _
        getAndToken, getStrNameToken("cy3"), getLeConditionOperator, getStrNameToken("cy4")
    '    添加 where
    addElemToArray meanTokens, _
        getWhereToken, getStrNameToken("cz1"), getLeConditionOperator, getStrNameToken("cz2"), _
        getAndToken, getStrNameToken("cz3"), getLeConditionOperator, getStrNameToken("cz4")
    '    添加 group by
    addElemToArray meanTokens, _
        getGroupByToken, getStrNameToken("bx1"), getAscToken, _
        getCommaToken, getStrNameToken("bx2"), getDescToken, createShareLineCommentToken("--c2"), _
        getWithToken, getRollupToken
    '    添加 order by
    addElemToArray meanTokens, _
        getOrderByToken, getStrNameToken("ox1"), getAscToken, _
        getCommaToken, getStrNameToken("ox2"), getDescToken
    '    添加 having
    addElemToArray meanTokens, _
        getHavingToken, getStrNameToken("hz1"), getLeConditionOperator, getStrNameToken("hz2"), _
        getAndToken, getStrNameToken("hz3"), getLeConditionOperator, getStrNameToken("hz4")
    '    添加 limit
    addElemToArray meanTokens, _
        getLimitToken, createNumToken("12"), getOffsetToken, createNumToken("100")

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 执行测试方法
    Set elem = analyzer.parseSelect(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 37
    Debug.Assert strArr.Item(0)  = "select --c1"
    Debug.Assert strArr.Item(1)  = "    distinct"
    Debug.Assert strArr.Item(2)  = "    x1,"
    Debug.Assert strArr.Item(3)  = "    x2"
    Debug.Assert strArr.Item(4)  = "from"
    Debug.Assert strArr.Item(5)  = "    tableX --c1"
    Debug.Assert strArr.Item(6)  = "        partition (x1, x2)"
    Debug.Assert strArr.Item(7)  = "    as t1 --c2"
    Debug.Assert strArr.Item(8)  = "        use key"
    Debug.Assert strArr.Item(9)  = "            for group by (x1, x2),"
    Debug.Assert strArr.Item(10) = "        force key"
    Debug.Assert strArr.Item(11) = "            for group by (x3, x4),"
    Debug.Assert strArr.Item(12) = "    table2 t2"
    Debug.Assert strArr.Item(13) = "left join"
    Debug.Assert strArr.Item(14) = "    table3 as t3"
    Debug.Assert strArr.Item(15) = "on"
    Debug.Assert strArr.Item(16) = "    cx1 <= cx2"
    Debug.Assert strArr.Item(17) = "    and cx3 <= cx4"
    Debug.Assert strArr.Item(18) = "left join"
    Debug.Assert strArr.Item(19) = "    table4 as t4"
    Debug.Assert strArr.Item(20) = "on"
    Debug.Assert strArr.Item(21) = "    cy1 <= cy2"
    Debug.Assert strArr.Item(22) = "    and cy3 <= cy4"
    Debug.Assert strArr.Item(23) = "where"
    Debug.Assert strArr.Item(24) = "    cz1 <= cz2"
    Debug.Assert strArr.Item(25) = "    and cz3 <= cz4"
    Debug.Assert strArr.Item(26) = "group by"
    Debug.Assert strArr.Item(27) = "    bx1 asc,"
    Debug.Assert strArr.Item(28) = "    bx2 desc --c2"
    Debug.Assert strArr.Item(29) = "    with rollup"
    Debug.Assert strArr.Item(30) = "order by"
    Debug.Assert strArr.Item(31) = "    ox1 asc,"
    Debug.Assert strArr.Item(32) = "    ox2 desc"
    Debug.Assert strArr.Item(33) = "having"
    Debug.Assert strArr.Item(34) = "    hz1 <= hz2"
    Debug.Assert strArr.Item(35) = "    and hz3 <= hz4"
    Debug.Assert strArr.Item(36) = "limit 12 offset 100"

    ' ==============================
    ' 2. 扫描器内:
    '        + select + STR_NAME
    '        + from
    '                  + (
    '                  + select + STR_NAME
    '                  + from + STR_NAME
    '                  + )
    '        + where
    '              + STR_NAME + <= + STR_NAME
    '              + and + not + exists + (
    '                  + select + STR_NAME
    '                  + from + STR_NAME
    '              + )
    '        + ) (强制停止)
    '    测试: 表定义 + 条件中都包含子查询
    ' ==============================
    ' 1. 创建扫描器
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(100)
    '    添加筛选项
    addElemToArray meanTokens, _
        getSelectToken, getStrNameToken("a2"), getFromToken, _
        _
        getLeftParenthesisToken, getSelectToken, getStrNameToken("b1"), getFromToken, getStrNameToken("table2"), getRightParenthesisToken, _
        _
        getWhereToken, getStrNameToken("q1"), getLeConditionOperator, getStrNameToken("q2"), _
        _
        getAndToken, getNotToken, getExistsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("e1"), getFromToken, getStrNameToken("subTable"), getRightParenthesisToken, _
        _
        getAndToken, getStrNameToken("q3"), getLeConditionOperator, getStrNameToken("q4"), _
        _
        getRightParenthesisToken

    addElemToArray meanTokens, _
        getUnionToken

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 执行测试方法
    Set elem = analyzer.parseSelect(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 18
    Debug.Assert strArr.Item(0)  = "select"
    Debug.Assert strArr.Item(1)  = "    a2"
    Debug.Assert strArr.Item(2)  = "from"
    Debug.Assert strArr.Item(3)  = "    ("
    Debug.Assert strArr.Item(4)  = "        select"
    Debug.Assert strArr.Item(5)  = "            b1"
    Debug.Assert strArr.Item(6)  = "        from"
    Debug.Assert strArr.Item(7)  = "            table2"
    Debug.Assert strArr.Item(8)  = "    )"
    Debug.Assert strArr.Item(9)  = "where"
    Debug.Assert strArr.Item(10) = "    q1 <= q2"
    Debug.Assert strArr.Item(11) = "    and not exists ("
    Debug.Assert strArr.Item(12) = "            select"
    Debug.Assert strArr.Item(13) = "                e1"
    Debug.Assert strArr.Item(14) = "            from"
    Debug.Assert strArr.Item(15) = "                subTable"
    Debug.Assert strArr.Item(16) = "        )"
    Debug.Assert strArr.Item(17) = "    and q3 <= q4"
End Sub

' 测试: 解析 select sql片段，并生成作用域
' 基础 if else 分支结构
Private Sub test_parseSelect_if_else()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: select
    '    测试只有一个 select 关键字
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelect(scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "select"
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT

    ' ==============================
    ' 2. 扫描器内: select + 一个注释 + distinct + STR_NAME + [,] + STR_NAME
    '    测试只有筛选项
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getSelectToken, createShareLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelect(scanner)
    ' 3. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0)  = "select --c1"
    Debug.Assert strArr.Item(1)  = "    distinct"
    Debug.Assert strArr.Item(2)  = "    x1,"
    Debug.Assert strArr.Item(3)  = "    x2"

    ' ==============================
    ' 3. 扫描器内: select + 一个注释 + distinct + STR_NAME + [,] + STR_NAME + 一个同行注释
    '             + )
    '    测试吸收筛选项后的注释 + 遇到右括号停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getSelectToken, createShareLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), createShareLineCommentToken("--c2"), _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelect(scanner)
    ' 3. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select --c1"
    Debug.Assert strArr.Item(1) = "    distinct"
    Debug.Assert strArr.Item(2) = "    x1,"
    Debug.Assert strArr.Item(3) = "    x2 --c2"

    ' ==============================
    ' 4. 测试下面这种括号嵌套
    '    select
    '    *
    '        (( -- 到这里会连续扫描出 2 个到左括号栈1
    '            from xxx
    '        )) -- 和左括号栈1 配对 2 个
    '        (((
    '            (( -- 到这里会连续扫描出 5 个到左括号栈1
    '                where a <= 1
    '            )) -- 和左括号栈2 配对 2 个，还剩余 3 个
    '            (( -- 到这里左括号栈1 会剩余 3 个，所以需要用左括号栈2 来保存
    '                order by b,c
    '            )) -- 和左括号栈2 配对
    '        ))) -- 和左括号栈1 配对
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getSelectToken, _
        getAsteriskToken, _
        getLeftParenthesisToken, getLeftParenthesisToken, createShareLineCommentToken("--c1"), _
        getFromToken, getStrNameToken("xxx"), _
        getRightParenthesisToken, getRightParenthesisToken, createShareLineCommentToken("--c2"), _
        getLeftParenthesisToken, getLeftParenthesisToken, getLeftParenthesisToken, createShareLineCommentToken("--c3"), _
        getLeftParenthesisToken, getLeftParenthesisToken, createShareLineCommentToken("--c4"), _
        getWhereToken, getStrNameToken("a"), getLeConditionOperator, createNumToken("1"), _
        getRightParenthesisToken, getRightParenthesisToken, createShareLineCommentToken("--c5"), _
        getLeftParenthesisToken, getLeftParenthesisToken, createShareLineCommentToken("--c6"), _
        getOrderByToken, getStrNameToken("b"), getCommaToken, getStrNameToken("c"), _
        getRightParenthesisToken, getRightParenthesisToken, createShareLineCommentToken("--c7"), _
        getRightParenthesisToken, getRightParenthesisToken, getRightParenthesisToken, _
        createShareLineCommentToken("--c8"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelect(scanner)
    ' 3. 检查结果
    Debug.Assert elem.getType = SqlTokenType.KEY_SELECT
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 17
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    *"
    Debug.Assert strArr.Item(2) = "(( --c1"
    Debug.Assert strArr.Item(3) = "    from"
    Debug.Assert strArr.Item(4) = "        xxx"
    Debug.Assert strArr.Item(5) = ")) --c2"
    Debug.Assert strArr.Item(6) = "((( --c3"
    Debug.Assert strArr.Item(7) = "    (( --c4"
    Debug.Assert strArr.Item(8) = "        where"
    Debug.Assert strArr.Item(9) = "            a <= 1"
    Debug.Assert strArr.Item(10) = "    )) --c5"
    Debug.Assert strArr.Item(11) = "    (( --c6"
    Debug.Assert strArr.Item(12) = "        order by"
    Debug.Assert strArr.Item(13) = "            b,"
    Debug.Assert strArr.Item(14) = "            c"
    Debug.Assert strArr.Item(15) = "    )) --c7"
    Debug.Assert strArr.Item(16) = "))) --c8"

End Sub

' 测试: 各类型sql的组成部分的通用解析
'       这里暂时只测试 select 的解析
Private Sub test_parseSqlComposition_select()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim selectAnalyzer As SqlFormatterSelectSubEAnalyzer
    Set selectAnalyzer = New SqlFormatterSelectSubEAnalyzer
    selectAnalyzer.Init analyzer

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 扫描器内: select 
    '            + 一个 UNION
    '    测试: 只有各组成部分的开头元素 + UNION 不会被解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "select"

    ' ==============================
    ' 2. 扫描器内: select
    '            + From
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getFromToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "from"

    ' ==============================
    ' 3. 扫描器内: select
    '            + Join
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getJoinToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "join"

    ' ==============================
    ' 4. 扫描器内: select
    '            + where
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getWhereToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "where"

    ' ==============================
    ' 5. 扫描器内: select
    '            + on
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getOnToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "on"

    ' ==============================
    ' 6. 扫描器内: select
    '             + group by
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getGroupByToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "group by"

    ' ==============================
    ' 7. 扫描器内: select
    '            + having
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getHavingToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "having"

    ' ==============================
    ' 8. 扫描器内: select
    '            + order by
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getOrderByToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "order by"

    ' ==============================
    ' 9. 扫描器内: select
    '            + limit
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getLimitToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "limit"

    ' ==============================
    ' 10. 扫描器内: select
    '            + from
    '            + 一个表定义(tableName) + 一个注释 + 一个 partition
    '            + as + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getFromToken, _
        _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "from"
    Debug.Assert strArr.Item(2) = "    tableX --c1"
    Debug.Assert strArr.Item(3) = "        partition (x1, x2)"
    Debug.Assert strArr.Item(4) = "    as t1 --c2"

    ' ==============================
    ' 11. 扫描器内: select 
    '            + join
    '            + 一个表定义(tableName) + 一个注释 + 一个 partition
    '            + as + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getJoinToken, _
        _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "join"
    Debug.Assert strArr.Item(2) = "    tableX --c1"
    Debug.Assert strArr.Item(3) = "        partition (x1, x2)"
    Debug.Assert strArr.Item(4) = "    as t1 --c2"

    ' ==============================
    ' 12. 扫描器内: select
    '             + where + STR_NAME + <= + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getWhereToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "where"
    Debug.Assert strArr.Item(2) = "    x1 <= x2"

    ' ==============================
    ' 13. 扫描器内: select
    '             + on + STR_NAME + <= + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getOnToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "on"
    Debug.Assert strArr.Item(2) = "    x1 <= x2"

    ' ==============================
    ' 14. 扫描器内: select
    '             + groupby + STR_NAME + ASC + [,] + STR_NAME + DESC + 一个注释
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getGroupByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getDescToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "group by"
    Debug.Assert strArr.Item(2) = "    x1 asc,"
    Debug.Assert strArr.Item(3) = "    x2 desc --c1"

    ' ==============================
    ' 15. 扫描器内: select
    '             + having + STR_NAME + <= + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getHavingToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "having"
    Debug.Assert strArr.Item(2) = "    x1 <= x2"

    ' ==============================
    ' 16. 扫描器内: select
    '             + orderBy + STR_NAME + ASC + [,] + STR_NAME + DESC
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getOrderByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getDescToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "order by"
    Debug.Assert strArr.Item(2) = "    x1 asc,"
    Debug.Assert strArr.Item(3) = "    x2 desc"

    ' ==============================
    ' 17. 扫描器内: select
    '             + Limit + 一个数字 + offset + 一个数字 (LIMIT row_count OFFSET offset)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getLimitToken, _
        createNumToken("12"), getOffsetToken, createNumToken("100"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseSqlComposition(selectAnalyzer, scanner)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "limit 12 offset 100"

End Sub

' 解析select关键字及其筛选项
Private Sub test_parseSelectItemsEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: select
    '    测试: 获取 select 后，扫描器用完
    ' ==============================
    ' 1. 创建扫描器
    Set token = getSelectToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: select + UNION
    '    测试: 获取 select 后没有列对象，能够正常停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "select"

    ' ==============================
    ' 3. 扫描器内:  select + 一个同行注释 + UNION
    '    测试: 获取 select 后没有列对象，能够正常停止，并吸收同行注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createShareLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "select --c1"

    ' ==============================
    ' 4. 扫描器内:  select + 一个单行注释 + distinct
    '    测试: 获取 select 的筛选行为关键字后，扫描器结束
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createSingleLineCommentToken("--c1"), getDistinctToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    distinct"

    ' ==============================
    ' 4.2 扫描器内:  select + 一个注释
    '                + distinct + 一个同行注释 + 一个单行注释
    '                + distinct + 一个同行注释 + 一个单行注释
    '                + 一个 UNION
    '    测试: 多个筛选关键字可以被吸收 + 多个筛选关键子之间的注释可以被吸收
    '          + 筛选关键字后的一个同行注释可以被吸收 + 筛选关键字和下一个作用域间的注释不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createShareLineCommentToken("--c1"), _
        getDistinctToken, createShareLineCommentToken("--c2"), createSingleLineCommentToken("--c3"), _
        getDistinctToken, createShareLineCommentToken("--c4"), createSingleLineCommentToken("--c5"), _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c5"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select --c1"
    Debug.Assert strArr.Item(1) = "    distinct --c2"
    Debug.Assert strArr.Item(2) = "    --c3"
    Debug.Assert strArr.Item(3) = "    distinct --c4"

    ' ==============================
    ' 5. 扫描器内:  select + 一个注释 + distinct + UNION
    '    测试: 获取 select 后，无法解析出第一个列对象，能够正常停止，并吸收注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createShareLineCommentToken("--c1"), getDistinctToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "select --c1"
    Debug.Assert strArr.Item(1) = "    distinct"

    ' ==============================
    ' 6. 扫描器内: select + 一个单行注释 + distinct + STR_NAME
    '    测试: 获取 select 后只有一个列对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createSingleLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    distinct"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 7. 扫描器内: select + 一个单行注释 + distinct + STR_NAME + )
    '    测试: 获取 select 后只有一个列对象 + 遇到 ) 停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createSingleLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    distinct"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 8. 扫描器内: select + 一个单行注释 + distinct + STR_NAME + UNION
    '    测试: 获取 select 后只有一个列对象 + 遇到匹配停止关键字
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createSingleLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    distinct"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 9. 扫描器内: select + 一个单行注释 + distinct + STR_NAME + [,]
    '    测试: 获取 select 后只有一个列对象 + 逗号可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createSingleLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    distinct"
    Debug.Assert strArr.Item(3) = "    x1,"

    ' ==============================
    ' 10. 扫描器内: select + 一个单行注释 + distinct + STR_NAME + 一个注释 + [,] + 一个注释
    '    测试: 获取 select 后只有一个列对象 + 逗号可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createSingleLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), _
        createShareLineCommentToken("--c2"), getCommaToken, createShareLineCommentToken("--c3"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    distinct"
    Debug.Assert strArr.Item(3) = "    x1 --c2"
    Debug.Assert strArr.Item(4) = "    , --c3"

    ' ==============================
    ' 11. 扫描器内:  select + 一个单行注释 + distinct + STR_NAME + [,] + UNION
    '    测试: 获取 select 后只有一个列对象 + 逗号可以被吸收 + 第二个列对象无法被解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createSingleLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), getCommaToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    distinct"
    Debug.Assert strArr.Item(3) = "    x1,"

    ' ==============================
    ' 12. 扫描器内: select + 一个单行注释 + distinct + STR_NAME + [,] + STR_NAME
    '    测试: 获取 select 后只有一个列对象 + 逗号可以被吸收 + 第二个列对象无法被解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, createSingleLineCommentToken("--c1"), getDistinctToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    distinct"
    Debug.Assert strArr.Item(3) = "    x1,"
    Debug.Assert strArr.Item(4) = "    x2"

    ' ==============================
    ' 13. 扫描器内: select
    '             + STR_NAME + [+]
    '                      + case
    '                      + when + STR_NAME + Then + STR_NAME
    '                      + end
    '             + [,]
    '             + STR_NAME + <= + STR_NAME + and + STR_NAME + <= + STR_NAME
    '             + UNION
    '    测试: 解析多行列对象 + 一个条件对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getStrNameToken("y1"), getPlusToken, _
        getCaseToken, _
        getWhenToken, getStrNameToken("x1"), getThenToken, getStrNameToken("x2"), _
        getEndToken, _
        _
        getCommaToken, _
        _
        getStrNameToken("y1"), getLeConditionOperator, getStrNameToken("y2"), _
        getAndToken, getStrNameToken("y3"), getLeConditionOperator, getStrNameToken("y4"), _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 7
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    y1 +"
    Debug.Assert strArr.Item(2) = "        case"
    Debug.Assert strArr.Item(3) = "            when x1 then x2"
    Debug.Assert strArr.Item(4) = "        end,"
    Debug.Assert strArr.Item(5) = "    y1 <= y2"
    Debug.Assert strArr.Item(6) = "    and y3 <= y4"

    ' ==============================
    ' 14. 扫描器内: select
    '             + 一个同行注释 + 一个单行注释
    '             + distinct + 一个同行注释
    '             + 一个单行注释
    '             + STR_NAME + 同行注释 + 单行注释
    '             + [,] + 同行注释 + 单行注释
    '             + STR_NAME + 同行注释 + 单行注释 + [,]
    '             + STR_NAME + 同行注释
    '             + 单行注释 + union
    '    测试: 每个列前面的注释 + 逗号前的注释，最后一个列右侧的同行注释可以被吸收
    '         + 下一个作用域前面的单行注释不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getDistinctToken, createShareLineCommentToken("--c3"), _
        createSingleLineCommentToken("--c4"), _
        _
        getStrNameToken("x1"), createShareLineCommentToken("--c5"), createSingleLineCommentToken("--c6"), _
        getCommaToken, createShareLineCommentToken("--c7"), createSingleLineCommentToken("--c8"), _
        getStrNameToken("x2"), createShareLineCommentToken("--c9"), createSingleLineCommentToken("--c10"), _
        getCommaToken, _
        getStrNameToken("x3"), createShareLineCommentToken("--c11"), _
        _
        createSingleLineCommentToken("--c12"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemsEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c12"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 12
    Debug.Assert strArr.Item(0)  = "select --c1"
    Debug.Assert strArr.Item(1)  = "    --c2"
    Debug.Assert strArr.Item(2)  = "    distinct --c3"
    Debug.Assert strArr.Item(3)  = "    --c4"
    Debug.Assert strArr.Item(4)  = "    x1 --c5"
    Debug.Assert strArr.Item(5)  = "    --c6"
    Debug.Assert strArr.Item(6)  = "    , --c7"
    Debug.Assert strArr.Item(7)  = "    --c8"
    Debug.Assert strArr.Item(8)  = "    x2 --c9"
    Debug.Assert strArr.Item(9)  = "    --c10"
    Debug.Assert strArr.Item(10) = "    ,"
    Debug.Assert strArr.Item(11) = "    x3 --c11"

End Sub

' 测试: 处理 select 筛选项
Private Sub test_parseSelectItemInfo()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: UNION
    '    测试: 无法解析出筛选项
    ' ==============================
    ' 1. 创建一个空扫描器
    Set token = getUnionToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemInfo(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.getNext Is token

    ' ==============================
    ' 2. 扫描器内: STR_NAME
    '    测试: 只解析 mixedColExpr
    ' ==============================
    ' 1. 创建一个空扫描器
    Set token = getStrNameToken("c1")
    Set scanner = getObjArrayRefScanner(token, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemInfo(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 3. 扫描器内: STR_NAME + STR_NAME
    '    测试: 解析 mixedColExpr aliasName, aliasName=name
    ' ==============================
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("c1"), getStrNameToken("cx"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemInfo(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "c1 cx"

    ' ==============================
    ' 4. 扫描器内: STR_NAME + STR_STR
    '    测试: 解析 mixedColExpr aliasName, aliasName=str
    ' ==============================
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("c1"), createStrToken("abc"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemInfo(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "c1 'abc'"

    ' ==============================
    ' 5. 扫描器内: STR_NAME + as + STR_NAME
    '    测试: 解析 mixedColExpr aliasName, aliasName=as name
    ' ==============================
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("c1"), getAsToken, getStrNameToken("cx"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemInfo(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "c1 as cx"

    ' ==============================
    ' 6. 扫描器内: STR_NAME + as + STR_STR
    '    测试: 解析 mixedColExpr aliasName, aliasName=as str
    ' ==============================
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("c1"), getAsToken, createStrToken("abc"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemInfo(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "c1 as 'abc'"

    ' ==============================
    ' 7. 扫描器内: STR_NAME +as + STR_NAME + UNION
    '    测试: 解析 mixedColExpr aliasName, aliasName=as name + 扫描器未用完
    ' ==============================
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("c1"), getAsToken, getStrNameToken("cx"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemInfo(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.toString(config) = "c1 as cx"

    ' ==============================
    ' 8. 扫描器内:
'                 STR_NAME + [+]
'                      + case
'                      + when + STR_NAME + Then + STR_NAME
'                      + end
    '             + as + STR_NAME
    '    测试: 解析多行列对象
    ' ==============================
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("y1"), getPlusToken, _
        getCaseToken, _
        getWhenToken, getStrNameToken("x1"), getThenToken, getStrNameToken("x2"), _
        getEndToken, _
        _
        getAsToken, getStrNameToken("cx"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseSelectItemInfo(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "y1 +"
    Debug.Assert strArr.Item(1) = "    case"
    Debug.Assert strArr.Item(2) = "        when x1 then x2"
    Debug.Assert strArr.Item(3) = "    end as cx"

End Sub

' 测试: 吸收列后面的别名
Private Sub test_parseColAliasName()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner

    Dim joinKey As ISqlFormatterElem

    Dim token As SqlToken
    Dim col As SqlToken
    Dim containerCol As SqlFormatterContainerCol
    Dim elem As ISqlFormatterElem

    ' ==============================
    ' 1. 扫描器内: 空
    ' ==============================
    ' 1. 创建一个空扫描器
    Set scanner = getObjArrayRefScanner(getNotToken)
    scanner.toNext

    ' 2. 创建一个列对象
    Set col = getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.parseColAliasName(scanner, col)

    Debug.Assert elem Is col

    ' ==============================
    ' 2. 扫描器内没有 STR_NAME 和 as，返回原始列对象
    ' ==============================
    Set scanner = getObjArrayRefScanner(getNotToken)

    ' 2. 创建一个列对象
    Set col = getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.parseColAliasName(scanner, col)

    Debug.Assert elem Is col
    Debug.Assert scanner.ActiveIndex = 0

    ' ==============================
    ' 3. 列对象是一个容器列 + 扫描器有一个 STR_NAME，返回原始列对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("y1"))

    ' 2. 创建一个容器列对象
    Set containerCol = New SqlFormatterContainerCol
    containerCol.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.parseColAliasName(scanner, containerCol)

    ' 4. 检查结果
    Debug.Assert elem Is containerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 y1"

    ' ==============================
    ' 4. 列对象是一个 SqlToken + 扫描器有一个 STR_NAME，返回一个容器列对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("y1"))

    ' 2. 创建一个容器列对象
    Set col = getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.parseColAliasName(scanner, col)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 y1"

    ' ==============================
    ' 5. 列对象是一个容器列 + 扫描器有一个 As，返回原始列对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getAsToken)

    ' 2. 创建一个容器列对象
    Set containerCol = New SqlFormatterContainerCol
    containerCol.ISqlFormatterElem_addComponent getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.parseColAliasName(scanner, containerCol)

    ' 4. 检查结果
    Debug.Assert elem Is containerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 as"

    ' ==============================
    ' 6. 列对象是一个 SqlToken + 扫描器有一个 As，返回容器列对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getAsToken)

    ' 2. 创建一个容器列对象
    Set col = getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.parseColAliasName(scanner, col)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 as"

    ' ==============================
    ' 7. 列对象是一个 SqlToken + 扫描器有一个 As + 一个非 STR_NAME ，返回容器列对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getAsToken, getNotToken)

    ' 2. 创建一个容器列对象
    Set col = getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.parseColAliasName(scanner, col)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_NOT
    Debug.Assert elem.toString(config) = "x1 as"

    ' ==============================
    ' 8. 列对象是一个 SqlToken + 扫描器有一个 As + 一个 STR_NAME ，返回容器列对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getAsToken, getStrNameToken("y1"))

    ' 2. 创建一个容器列对象
    Set col = getStrNameToken("x1")

    ' 3. 执行测试方法
    Set elem = analyzer.parseColAliasName(scanner, col)

    ' 4. 检查结果
    Debug.Assert Not elem Is col
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 as y1"

End Sub

' 测试: 解析 udpate sql片段，并生成作用域
Private Sub test_parseUpdate()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内:
    '    update + 一个同行注释1
    '         + low_priority
    '         + ignore
    '         + STR_NAME(tableX) + [,]
    '         + ( select * from table1 )
    '    + set
    '         + a1 = "abc" + [,]
    '         + a2 = xyz
    '    + where
    '        + STR_NAME + <= + STR_NAME
    '        + and + STR_NAME + <= + STR_NAME
    '    + order by
    '        + STR_NAME + ASC
    '        + [,] + STR_NAME + DESC
    '    + Limit + 一个数字 + offset + 一个数字 (LIMIT row_count OFFSET offset)
    ' 基础结构测试
    ' ==============================
    ' 1. 创建扫描器
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(100)
    '    添加筛选项
    addElemToArray meanTokens, _
        getUpdateToken, createShareLineCommentToken("--c1"), _
        getLowPriorityToken, getIgnoreToken, _
        getStrNameToken("tableX"), _
        _
        getCommaToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getAsteriskToken, getFromToken, getStrNameToken("table1"), _
        getRightParenthesisToken
    '    添加 set
    addElemToArray meanTokens, _
        getSetToken, _
        getStrNameToken("a1"), getEqConditionOperator, createStrToken("abc"), getCommaToken, _
        getStrNameToken("a2"), getEqConditionOperator, getStrNameToken("xyz")
    '    添加 where
    addElemToArray meanTokens, _
        getWhereToken, getStrNameToken("cz1"), getLeConditionOperator, getStrNameToken("cz2"), _
        getAndToken, getStrNameToken("cz3"), getLeConditionOperator, getStrNameToken("cz4")
    '    添加 order by
    addElemToArray meanTokens, _
        getOrderByToken, getStrNameToken("ox1"), getAscToken, _
        getCommaToken, getStrNameToken("ox2"), getDescToken
    '    添加 limit
    addElemToArray meanTokens, _
        getLimitToken, createNumToken("12"), getOffsetToken, createNumToken("100")

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdate(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_UPDATE
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 20
    Debug.Assert strArr.Item(0)  = "update --c1"
    Debug.Assert strArr.Item(1)  = "    low_priority"
    Debug.Assert strArr.Item(2)  = "    ignore"
    Debug.Assert strArr.Item(3)  = "    tableX,"
    Debug.Assert strArr.Item(4)  = "    ("
    Debug.Assert strArr.Item(5)  = "        select"
    Debug.Assert strArr.Item(6)  = "            *"
    Debug.Assert strArr.Item(7)  = "        from"
    Debug.Assert strArr.Item(8)  = "            table1"
    Debug.Assert strArr.Item(9)  = "    )"
    Debug.Assert strArr.Item(10) = "set"
    Debug.Assert strArr.Item(11) = "    a1 = 'abc',"
    Debug.Assert strArr.Item(12) = "    a2 = xyz"
    Debug.Assert strArr.Item(13) = "where"
    Debug.Assert strArr.Item(14) = "    cz1 <= cz2"
    Debug.Assert strArr.Item(15) = "    and cz3 <= cz4"
    Debug.Assert strArr.Item(16) = "order by"
    Debug.Assert strArr.Item(17) = "    ox1 asc,"
    Debug.Assert strArr.Item(18) = "    ox2 desc"
    Debug.Assert strArr.Item(19) = "limit 12 offset 100"
End Sub

' 测试: 解析update关键字及其下的所有表定义
Private Sub test_parseUpdateTableReferencesEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: update
    '    测试: 获取 update 后，扫描器用完
    ' ==============================
    ' 1. 创建扫描器
    Set token = getUpdateToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: update + UNION
    '    测试: 获取 update 后没有列对象，能够正常停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "update"

    ' ==============================
    ' 3. 扫描器内:  update + 一个同行注释 + UNION
    '    测试: 获取 update 后没有列对象，能够正常停止，并吸收同行注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createShareLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "update --c1"

    ' ==============================
    ' 4. 扫描器内:  update + 一个单行注释 + low_priority
    '    测试: 获取 update 的筛选行为关键字后，扫描器结束
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"

    ' ==============================
    ' 4.2 扫描器内:  update + 一个注释
    '                + low_priority + 一个同行注释 + 一个单行注释
    '                + ignore + 一个同行注释 + 一个单行注释
    '                + 一个 UNION
    '    测试: 多个筛选关键字可以被吸收 + 多个筛选关键子之间的注释可以被吸收
    '          + 筛选关键字后的一个同行注释可以被吸收 + 筛选关键字和下一个作用域间的注释不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createShareLineCommentToken("--c1"), _
        getLowPriorityToken, createShareLineCommentToken("--c2"), createSingleLineCommentToken("--c3"), _
        getIgnoreToken, createShareLineCommentToken("--c4"), createSingleLineCommentToken("--c5"), _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c5"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "update --c1"
    Debug.Assert strArr.Item(1) = "    low_priority --c2"
    Debug.Assert strArr.Item(2) = "    --c3"
    Debug.Assert strArr.Item(3) = "    ignore --c4"

    ' ==============================
    ' 5. 扫描器内:  update + 一个注释 + low_priority + UNION
    '    测试: 获取 update 后，无法解析出第一个列对象，能够正常停止，并吸收注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createShareLineCommentToken("--c1"), getLowPriorityToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "update --c1"
    Debug.Assert strArr.Item(1) = "    low_priority"

    ' ==============================
    ' 6. 扫描器内: update + 一个单行注释 + low_priority + STR_NAME
    '    测试: 获取 low_priority 后只有一个表名(表定义)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 7. 扫描器内: update + 一个单行注释 + low_priority + STR_NAME + )
    '    测试: 获取 update 后只有一个表名(表定义) + 遇到 ) 停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 8. 扫描器内: update + 一个单行注释 + low_priority + STR_NAME + UNION
    '    测试: 获取 update 后只有一个表名(表定义) + 遇到匹配停止关键字
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 9. 扫描器内: update + 一个单行注释 + low_priority + STR_NAME + [,]
    '    测试: 获取 update 后只有一个表名(表定义) + 逗号可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1,"

    ' ==============================
    ' 10. 扫描器内: update + 一个单行注释 + low_priority + STR_NAME + 一个注释 + [,] + 一个注释
    '    测试: 获取 update 后只有一个表名(表定义) + 逗号可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), _
        createShareLineCommentToken("--c2"), getCommaToken, createShareLineCommentToken("--c3"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1 --c2"
    Debug.Assert strArr.Item(4) = "    , --c3"

    ' ==============================
    ' 11. 扫描器内:  update + 一个单行注释 + low_priority + STR_NAME + [,] + UNION
    '    测试: 获取 update 后只有一个表名(表定义) + 逗号可以被吸收 + 第二个表定义无法被解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getCommaToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1,"

    ' ==============================
    ' 12. 扫描器内: update + 一个单行注释 + low_priority + STR_NAME + [,] + STR_NAME
    '    测试: 获取 update 后一个表名(表定义) + 逗号可以被吸收 + 一个表名(表定义)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1,"
    Debug.Assert strArr.Item(4) = "    x2"

    ' ==============================
    ' 13. 扫描器内: update + 一个单行注释 + low_priority 
    '             + STR_NAME + [,]
    '             + (
    '                 select STR_NAME from STR_NAME
    '             + )
    '    测试: 获取 update 后一个表名(表定义) + 逗号可以被吸收 + 一个表名(子查询)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        _
        getStrNameToken("x1"), getCommaToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("x2"), getFromToken, getStrNameToken("table1"), _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 10
    Debug.Assert strArr.Item(0) = "update"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1,"
    Debug.Assert strArr.Item(4) = "    ("
    Debug.Assert strArr.Item(5) = "        select"
    Debug.Assert strArr.Item(6) = "            x2"
    Debug.Assert strArr.Item(7) = "        from"
    Debug.Assert strArr.Item(8) = "            table1"
    Debug.Assert strArr.Item(9) = "    )"

    ' ==============================
    ' 14. 扫描器内: update
    '             + 一个同行注释 + 一个单行注释
    '             + low_priority + 一个同行注释
    '             + 一个单行注释
    '             + STR_NAME + 同行注释 + 单行注释
    '             + [,] + 同行注释 + 单行注释
    '             + STR_NAME + 同行注释 + 单行注释 + [,]
    '             + STR_NAME + 同行注释
    '             + 单行注释 + union
    '    测试: 每个表定义前面的注释 + 逗号前的注释，最后一个表定义右侧的同行注释可以被吸收
    '         + 下一个作用域前面的单行注释不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUpdateToken, _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getLowPriorityToken, createShareLineCommentToken("--c3"), _
        createSingleLineCommentToken("--c4"), _
        _
        getStrNameToken("x1"), createShareLineCommentToken("--c5"), createSingleLineCommentToken("--c6"), _
        getCommaToken, createShareLineCommentToken("--c7"), createSingleLineCommentToken("--c8"), _
        getStrNameToken("x2"), createShareLineCommentToken("--c9"), createSingleLineCommentToken("--c10"), _
        getCommaToken, _
        getStrNameToken("x3"), createShareLineCommentToken("--c11"), _
        _
        createSingleLineCommentToken("--c12"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateTableReferencesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c12"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 12
    Debug.Assert strArr.Item(0)  = "update --c1"
    Debug.Assert strArr.Item(1)  = "    --c2"
    Debug.Assert strArr.Item(2)  = "    low_priority --c3"
    Debug.Assert strArr.Item(3)  = "    --c4"
    Debug.Assert strArr.Item(4)  = "    x1 --c5"
    Debug.Assert strArr.Item(5)  = "    --c6"
    Debug.Assert strArr.Item(6)  = "    , --c7"
    Debug.Assert strArr.Item(7)  = "    --c8"
    Debug.Assert strArr.Item(8)  = "    x2 --c9"
    Debug.Assert strArr.Item(9)  = "    --c10"
    Debug.Assert strArr.Item(10) = "    ,"
    Debug.Assert strArr.Item(11) = "    x3 --c11"

End Sub

' 测试: 解析update的set作用域
Private Sub test_parseUpdateSetEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: set
    '    测试扫描器内只有 set 关键字，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getSetToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内:  set + UNION
    '    测试扫描器内有 set 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getSetToken
    Set scanner = getObjArrayRefScanner(token, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 3. 扫描器内: set + 一个同行注释
    '    测试扫描器内有 set 关键字 + 吸收关键字后面的同行注释，并且和 set 连接到同一行
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getSetToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_SET

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "set --c1"

    ' ==============================
    ' 4. 扫描器内: set + 一个单行注释 + UNION
    '    测试扫描器内有 set 关键字 + 吸收关键字后面的注释 + 非表定义内容停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getSetToken
    Set scanner = getObjArrayRefScanner(token, createSingleLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.value = "--c1"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "set"

    ' ==============================
    ' 5. 扫描器内: set + 一个同行注释 + STR_NAME
    '    测试扫描器内有 set 关键字 + 吸收关键字后面的注释 + 吸收这个 STR_NAME 作为一个不完整的赋值语句
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getSetToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_SET

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "set --c1"
    Debug.Assert strArr.Item(1) = "    x1"

    ' ==============================
    ' 6. 扫描器内: set + 一个同行注释 + STR_NAME + UNION
    '    测试扫描器内有 set 关键字 + 吸收关键字后面的注释
    '    + 吸收这个 STR_NAME 作为一个不完整的赋值语句
    '    + 遇到 union 停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getSetToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("x1"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_SET

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "set --c1"
    Debug.Assert strArr.Item(1) = "    x1"

    ' ==============================
    ' 7. 扫描器内: set + 一个同行注释
    '    + STR_NAME + [=] + STR_NAME
    '    + UNION
    '    测试扫描器内有 set 关键字 + 吸收关键字后面的注释 + 吸收一个完整的赋值语句
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getSetToken, createShareLineCommentToken("--c1"), _
        getStrNameToken("x1"), getEqConditionOperator, getStrNameToken("x2"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_SET

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "set --c1"
    Debug.Assert strArr.Item(1) = "    x1 = x2"

    ' ==============================
    ' 8. 扫描器内: set + 一个同行注释
    '    + STR_NAME + [=] + STR_NAME + [,]
    '    + STR_NAME + [=] + STR_NAME
    '    + UNION
    '    测试扫描器内有 set 关键字 + 吸收关键字后面的注释 + 吸收多个完整的赋值语句
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getSetToken, createShareLineCommentToken("--c1"), _
        getStrNameToken("x1"), getEqConditionOperator, getStrNameToken("x2"), _
        getCommaToken, _
        getStrNameToken("x3"), getEqConditionOperator, getStrNameToken("x4"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_SET

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "set --c1"
    Debug.Assert strArr.Item(1) = "    x1 = x2,"
    Debug.Assert strArr.Item(2) = "    x3 = x4"

    ' ==============================
    ' 9. 扫描器内: set + 同行注释1 + 单行注释2
    '    + STR_NAME + 同行注释3 + 单行注释4
    '    + [=] + 同行注释5 + 单行注释6
    '    + STR_NAME + 同行注释7 + 单行注释8
    '    + [,] + 同行注释9 + 单行注释10
    '    + STR_NAME + [=] + STR_NAME + 同行注释11 + 单行注释12
    '    + [,] + 同行注释13
    '    + 单行注释14 + UNION
    '    测试扫描器内有 set 关键字 + 吸收关键字后面的注释 + 吸收多个完整的赋值语句
    '    + 各部分的注释可以被吸收 + 下一个部分前的单行注释(14) 不会被吸收
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getSetToken, createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getStrNameToken("x1"), createShareLineCommentToken("--c3"), createSingleLineCommentToken("--c4"), _
        getEqConditionOperator, createShareLineCommentToken("--c5"), createSingleLineCommentToken("--c6"), _
        getStrNameToken("x2"), createShareLineCommentToken("--c7"), createSingleLineCommentToken("--c8"), _
        getCommaToken, createShareLineCommentToken("--c9"), createSingleLineCommentToken("--c10"), _
        _
        getStrNameToken("x3"), getEqConditionOperator, getStrNameToken("x4"), _
        createShareLineCommentToken("--c11"), createSingleLineCommentToken("--c12"), _
        _
        getCommaToken, createShareLineCommentToken("--c13"), createSingleLineCommentToken("--c14"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUpdateSetEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c14"

    Debug.Assert elem.getType = SqlTokenType.KEY_SET

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 13
    Debug.Assert strArr.Item(0)  = "set --c1"
    Debug.Assert strArr.Item(1)  = "    --c2"
    Debug.Assert strArr.Item(2)  = "    x1 --c3"
    Debug.Assert strArr.Item(3)  = "    --c4"
    Debug.Assert strArr.Item(4)  = "    = --c5"
    Debug.Assert strArr.Item(5)  = "    --c6"
    Debug.Assert strArr.Item(6)  = "    x2 --c7"
    Debug.Assert strArr.Item(7)  = "    --c8"
    Debug.Assert strArr.Item(8)  = "    , --c9"
    Debug.Assert strArr.Item(9)  = "    --c10"
    Debug.Assert strArr.Item(10) = "    x3 = x4 --c11"
    Debug.Assert strArr.Item(11) = "    --c12"
    Debug.Assert strArr.Item(12) = "    , --c13"
End Sub

' 测试: 解析 using
Private Sub test_parseUsingEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: using
    '            + STR_NAME + t1
    '            + [,]
    '            + STR_NAME + t2
    '            + UNION
    '    测试基础结构
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getUsingToken, getStrNameToken("table1"), getStrNameToken("t1"), _
        getCommaToken, _
        getStrNameToken("table2"), getStrNameToken("t2"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseUsingEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_USING

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "using"
    Debug.Assert strArr.Item(1) = "    table1 t1,"
    Debug.Assert strArr.Item(2) = "    table2 t2"

End Sub

' 测试: 解析 delete sql片段，并生成作用域
Private Sub test_parseDelete()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    ' ==============================
    ' 1. 扫描器内: delete + 一个单行注释
    '             + low_priority
    '             + STR_NAME + [.] + [*]
    '             + [,]
    '             + STR_NAME + [.] + [*]
    '             + from table1 t1, table2 t2
    '             + where
    '                   + t1.a1 = 'abc'
    '                   + and t2.a2 = 'xyz'
    '    测试: 获取 delete 的基础结构
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getPointToken, getAsteriskToken, _
        getCommaToken, _
        getStrNameToken("x2"), getPointToken, getAsteriskToken, _
        _
        getFromToken, getStrNameToken("table1"), getStrNameToken("t1"), _
        getCommaToken, getStrNameToken("table2"), getStrNameToken("t2"), _
        _
        getWhereToken, _
        getStrNameToken("t1"), getPointToken, getStrNameToken("a1"), getEqConditionOperator, createStrToken("abc"), _
        getAndToken, _
        getStrNameToken("t2"), getPointToken, getStrNameToken("a2"), getEqConditionOperator, createStrToken("xyz"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseDelete(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 11
    Debug.Assert strArr.Item(0)  = "delete"
    Debug.Assert strArr.Item(1)  = "    --c1"
    Debug.Assert strArr.Item(2)  = "    low_priority"
    Debug.Assert strArr.Item(3)  = "    x1.*,"
    Debug.Assert strArr.Item(4)  = "    x2.*"
    Debug.Assert strArr.Item(5)  = "from"
    Debug.Assert strArr.Item(6)  = "    table1 t1,"
    Debug.Assert strArr.Item(7)  = "    table2 t2"
    Debug.Assert strArr.Item(8)  = "where"
    Debug.Assert strArr.Item(9)  = "    t1.a1 = 'abc'"
    Debug.Assert strArr.Item(10) = "    and t2.a2 = 'xyz'"
End Sub

' 测试: 解析delete作用域的起始部分
Private Sub test_parseDeleteStartEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: delete
    '    测试: 获取 delete 后，扫描器用完
    ' ==============================
    ' 1. 创建扫描器
    Set token = getDeleteToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: delete + UNION
    '    测试: 获取 delete 后没有列对象，能够正常停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "delete"

    ' ==============================
    ' 3. 扫描器内:  delete + 一个同行注释 + UNION
    '    测试: 获取 delete 后没有列对象，能够正常停止，并吸收同行注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createShareLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "delete --c1"

    ' ==============================
    ' 4. 扫描器内:  delete + 一个单行注释 + low_priority
    '    测试: 获取 delete 的筛选行为关键字后，扫描器结束
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"

    ' ==============================
    ' 4.2 扫描器内:  delete + 一个注释
    '                + low_priority + 一个同行注释 + 一个单行注释
    '                + ignore + 一个同行注释 + 一个单行注释
    '                + quick + 一个同行注释 + 一个单行注释
    '                + 一个 UNION
    '    测试: 多个筛选关键字可以被吸收 + 多个筛选关键子之间的注释可以被吸收
    '          + 筛选关键字后的一个同行注释可以被吸收 + 筛选关键字和下一个作用域间的注释不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createShareLineCommentToken("--c1"), _
        getLowPriorityToken, createShareLineCommentToken("--c2"), createSingleLineCommentToken("--c3"), _
        getIgnoreToken, createShareLineCommentToken("--c4"), createSingleLineCommentToken("--c5"), _
        getQuickToken, createShareLineCommentToken("--c6"), createSingleLineCommentToken("--c7"), _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c7"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "delete --c1"
    Debug.Assert strArr.Item(1) = "    low_priority --c2"
    Debug.Assert strArr.Item(2) = "    --c3"
    Debug.Assert strArr.Item(3) = "    ignore --c4"
    Debug.Assert strArr.Item(4) = "    --c5"
    Debug.Assert strArr.Item(5) = "    quick --c6"

    ' ==============================
    ' 5. 扫描器内:  delete + 一个注释 + low_priority + UNION
    '    测试: 获取 delete 后，无法解析出第一个列对象，能够正常停止，并吸收注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createShareLineCommentToken("--c1"), getLowPriorityToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "delete --c1"
    Debug.Assert strArr.Item(1) = "    low_priority"

    ' ==============================
    ' 6. 扫描器内: delete + 一个单行注释 + low_priority + STR_NAME
    '    测试: 获取 low_priority 后只有一个表名(表定义)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 7. 扫描器内: delete + 一个单行注释 + low_priority + STR_NAME + )
    '    测试: 获取 delete 后只有一个表名(表定义) + 遇到 ) 停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.RIGHT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 8. 扫描器内: delete + 一个单行注释 + low_priority + STR_NAME + UNION
    '    测试: 获取 delete 后只有一个表名(表定义) + 遇到匹配停止关键字
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1"

    ' ==============================
    ' 9. 扫描器内: delete + 一个单行注释 + low_priority + STR_NAME + [,]
    '    测试: 获取 delete 后只有一个表名(表定义) + 逗号可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1,"

    ' ==============================
    ' 10. 扫描器内: delete + 一个单行注释 + low_priority + STR_NAME + 一个注释 + [,] + 一个注释
    '    测试: 获取 delete 后只有一个表名(表定义) + 逗号可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), _
        createShareLineCommentToken("--c2"), getCommaToken, createShareLineCommentToken("--c3"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1 --c2"
    Debug.Assert strArr.Item(4) = "    , --c3"

    ' ==============================
    ' 11. 扫描器内:  delete + 一个单行注释 + low_priority + STR_NAME + [,] + UNION
    '    测试: 获取 delete 后只有一个表名(表定义) + 逗号可以被吸收 + 第二个表定义无法被解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getCommaToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1,"

    ' ==============================
    ' 12. 扫描器内: delete + 一个单行注释 + low_priority + STR_NAME + [,] + STR_NAME
    '    测试: 获取 delete 后一个表名(表定义) + 逗号可以被吸收 + 一个表名(表定义)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1,"
    Debug.Assert strArr.Item(4) = "    x2"

    ' ==============================
    ' 13. 扫描器内: delete + 一个单行注释 + low_priority
    '             + STR_NAME + [.] + [*]
    '             + [,]
    '             + STR_NAME + [.] + [*]
    '    测试: 获取 delete 后一个[tbl_name.*] + 逗号可以被吸收 + 一个[tbl_name.*]
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getDeleteToken, createSingleLineCommentToken("--c1"), getLowPriorityToken, _
        getStrNameToken("x1"), getPointToken, getAsteriskToken, _
        getCommaToken, _
        getStrNameToken("x2"), getPointToken, getAsteriskToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseDeleteStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "delete"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"
    Debug.Assert strArr.Item(3) = "    x1.*,"
    Debug.Assert strArr.Item(4) = "    x2.*"

End Sub

' 测试: 解析 inset sql片段，并生成作用域
Private Sub test_parseInsert()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: insert + low_priority + ignore
    '            + into + STR_NAME(table1)
    '            + ( + c1, c2 + )
    '            + as + newRow + ( + x1, x2 + )
    '            + values ( 'v11', 'v12')
    '            + , ( 'v21', 'v22')
    '            + UNION
    '    测试: 获取 insert 的基础结构
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getInsertToken, getLowPriorityToken, getIgnoreToken, _
        _
        getIntoToken, getStrNameToken("table1"), _
        _
        getLeftParenthesisToken, getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getRightParenthesisToken, _
        _
        getAsToken, getStrNameToken("newRow"), _
        getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        _
        getValuesToken, _
        getLeftParenthesisToken, createStrToken("v11"), getCommaToken, createStrToken("v12"), getRightParenthesisToken, _
        getCommaToken, _ 
        getLeftParenthesisToken, createStrToken("v21"), getCommaToken, createStrToken("v22"), getRightParenthesisToken, _
        _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseInsert(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 20
    Debug.Assert strArr.Item(0)  = "insert"
    Debug.Assert strArr.Item(1)  = "    low_priority"
    Debug.Assert strArr.Item(2)  = "    ignore"
    Debug.Assert strArr.Item(3)  = "into"
    Debug.Assert strArr.Item(4)  = "    table1"
    Debug.Assert strArr.Item(5)  = "    ("
    Debug.Assert strArr.Item(6)  = "        c1,"
    Debug.Assert strArr.Item(7)  = "        c2"
    Debug.Assert strArr.Item(8)  = "    )"
    Debug.Assert strArr.Item(9)  = "as"
    Debug.Assert strArr.Item(10) = "    newRow (x1, x2)"
    Debug.Assert strArr.Item(11) = "values"
    Debug.Assert strArr.Item(12) = "    ("
    Debug.Assert strArr.Item(13) = "        'v11',"
    Debug.Assert strArr.Item(14) = "        'v12'"
    Debug.Assert strArr.Item(15) = "    ),"
    Debug.Assert strArr.Item(16) = "    ("
    Debug.Assert strArr.Item(17) = "        'v21',"
    Debug.Assert strArr.Item(18) = "        'v22'"
    Debug.Assert strArr.Item(19) = "    )"

    ' ==============================
    ' 2. 扫描器内: insert
    '            + into + STR_NAME(table1)
    '            + ( + c1, c2 + )
    '            + select * from tableX
    '            + on + DUPLICATE + KEY + UPDATE
    '            + c1 = 'abc',
    '            + c2 = 'xyz'
    '            + UNION
    '    测试: 插入值部分是子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getInsertToken, getLowPriorityToken, getIgnoreToken, _
        _
        getIntoToken, getStrNameToken("table1"), _
        _
        getLeftParenthesisToken, getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getRightParenthesisToken, _
        _
        getSelectToken, getAsteriskToken, getFromToken, getStrNameToken("tableX"), _
        _
        getOnToken, getDuplicateToken, getKeyToken, getUpdateToken, _
        getStrNameToken("c1"), getEqConditionOperator, createStrToken("abc"), getCommaToken, _
        getStrNameToken("c2"), getEqConditionOperator, createStrToken("xyz"), _
        _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseInsert(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 16
    Debug.Assert strArr.Item(0)  = "insert"
    Debug.Assert strArr.Item(1)  = "    low_priority"
    Debug.Assert strArr.Item(2)  = "    ignore"
    Debug.Assert strArr.Item(3)  = "into"
    Debug.Assert strArr.Item(4)  = "    table1"
    Debug.Assert strArr.Item(5)  = "    ("
    Debug.Assert strArr.Item(6)  = "        c1,"
    Debug.Assert strArr.Item(7)  = "        c2"
    Debug.Assert strArr.Item(8)  = "    )"
    Debug.Assert strArr.Item(9)  = "select"
    Debug.Assert strArr.Item(10) = "    *"
    Debug.Assert strArr.Item(11) = "from"
    Debug.Assert strArr.Item(12) = "    tableX"
    Debug.Assert strArr.Item(13) = "on duplicate key update"
    Debug.Assert strArr.Item(14) = "    c1 = 'abc',"
    Debug.Assert strArr.Item(15) = "    c2 = 'xyz'"
End Sub

' 测试: 解析insert作用域的起始部分
Private Sub test_parseInsertStartEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: insert
    '    测试: 获取 insert 后，扫描器用完
    ' ==============================
    ' 1. 创建扫描器
    Set token = getInsertToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: insert + UNION
    '    测试: 获取 insert 后没有列对象，能够正常停止
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getInsertToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "insert"

    ' ==============================
    ' 3. 扫描器内:  insert + 一个同行注释 + UNION
    '    测试: 获取 insert 后没有列对象，能够正常停止，并吸收同行注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getInsertToken, createShareLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "insert --c1"

    ' ==============================
    ' 4. 扫描器内:  insert + 一个单行注释 + low_priority
    '    测试: 获取 insert 的筛选行为关键字后，扫描器结束
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getInsertToken, createSingleLineCommentToken("--c1"), getLowPriorityToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "insert"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    low_priority"

    ' ==============================
    ' 4.2 扫描器内:  insert + 一个注释
    '                + low_priority + 一个同行注释 + 一个单行注释
    '                + ignore + 一个同行注释 + 一个单行注释
    '                + delayed + 一个同行注释 + 一个单行注释
    '                + high_priority + 一个同行注释 + 一个单行注释
    '                + 一个 UNION
    '    测试: 多个筛选关键字可以被吸收 + 多个筛选关键子之间的注释可以被吸收
    '          + 筛选关键字后的一个同行注释可以被吸收 + 筛选关键字和下一个作用域间的注释不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getInsertToken, createShareLineCommentToken("--c1"), _
        getLowPriorityToken, createShareLineCommentToken("--c2"), createSingleLineCommentToken("--c3"), _
        getIgnoreToken, createShareLineCommentToken("--c4"), createSingleLineCommentToken("--c5"), _
        getDelayedToken, createShareLineCommentToken("--c6"), createSingleLineCommentToken("--c7"), _
        getHighPriorityToken, createShareLineCommentToken("--c8"), createSingleLineCommentToken("--c9"), _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c9"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 8
    Debug.Assert strArr.Item(0) = "insert --c1"
    Debug.Assert strArr.Item(1) = "    low_priority --c2"
    Debug.Assert strArr.Item(2) = "    --c3"
    Debug.Assert strArr.Item(3) = "    ignore --c4"
    Debug.Assert strArr.Item(4) = "    --c5"
    Debug.Assert strArr.Item(5) = "    delayed --c6"
    Debug.Assert strArr.Item(6) = "    --c7"
    Debug.Assert strArr.Item(7) = "    high_priority --c8"

    ' ==============================
    ' 5. 扫描器内:  insert + 一个注释 + low_priority + UNION
    '    测试: 获取 insert 后，无法解析出第一个列对象，能够正常停止，并吸收注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getInsertToken, createShareLineCommentToken("--c1"), getLowPriorityToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertStartEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "insert --c1"
    Debug.Assert strArr.Item(1) = "    low_priority"
End Sub

' 测试: 解析 into 作用域
Private Sub test_parseIntoEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: into
    '    测试扫描器内只有 into 关键字，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getIntoToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: into + UNION
    '    测试扫描器内有 into 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getIntoToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv

    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert elem.toString(config) = "into"

    ' ==============================
    ' 3. 扫描器内: into + 一个同行注释
    '    测试扫描器内有 into 关键字 + 吸收关键字后面的同行注释并连接到 join 的后面
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getIntoToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "into --c1"

    ' ==============================
    ' 4. 扫描器内: into + 一个单行注释 + UNION
    '    测试扫描器内有 into 关键字 + 不会吸收下一个部分之前的单行注释
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getIntoToken, createSingleLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c1"

    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "into"

    ' ==============================
    ' 5. 扫描器内: into + 一个同行注释 + STR_NAME
    '    测试扫描器内有 into 关键字 + 吸收关键字后面的注释 + 吸收表定义
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getIntoToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("table1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "into --c1"
    Debug.Assert strArr.Item(1) = "    table1"

    ' ==============================
    ' 6. 扫描器内: into + 一个注释 + STR_NAME + UNION
    '    测试扫描器内有 into 关键字 + 吸收关键字后面的注释 + 吸收表定义
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getIntoToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("table1"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "into --c1"
    Debug.Assert strArr.Item(1) = "    table1"

    ' ==============================
    ' 7. 扫描器内: into
    '            + 一个表定义(tableName) + 一个注释 + 一个 partition
    '            + as + STR_NAME
    '    比较完整的表定义测试
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getIntoToken, _
        _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "into"
    Debug.Assert strArr.Item(1) = "    tableX --c1"
    Debug.Assert strArr.Item(2) = "        partition (x1, x2)"
    Debug.Assert strArr.Item(3) = "    as t1 --c2"

    ' ==============================
    ' 8. 扫描器内: into + 一个注释
    '          + STR_NAME + 一个同行注释
    '         + (
    '             STR_NAEM, STR_NAME
    '         + ) + 一个同行注释
    '         + UNION
    '    测试扫描器内有 into 关键字 + 吸收关键字后面的注释 + 吸收表定义 + 列名
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getIntoToken, createShareLineCommentToken("--c1"), _
        _
        createFuncNameToken("table1"), createShareLineCommentToken("--c2"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, createShareLineCommentToken("--c3"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "into --c1"
    Debug.Assert strArr.Item(1) = "    table1 --c2"
    Debug.Assert strArr.Item(2) = "    ("
    Debug.Assert strArr.Item(3) = "        x1,"
    Debug.Assert strArr.Item(4) = "        x2"
    Debug.Assert strArr.Item(5) = "    ) --c3"

    ' ==================================================
    '             修改配置: 列的逗号后面不需要换行
    ' ==================================================
    config.insertColListNeedLineBreakAfterComma = False

    ' ==============================
    ' 1. 扫描器内: into + 一个注释
    '          + STR_NAME + 一个同行注释
    '         + (
    '             STR_NAEM, STR_NAME
    '         + ) + 一个同行注释
    '         + UNION
    '    测试: 扫描器内有 into 关键字 + 吸收关键字后面的注释 + 吸收表定义 + 列名
    '       + 逗号后面不需要换行
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getIntoToken, createShareLineCommentToken("--c1"), _
        _
        createFuncNameToken("table1"), createShareLineCommentToken("--c2"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, createShareLineCommentToken("--c3"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIntoEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_INTO

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "into --c1"
    Debug.Assert strArr.Item(1) = "    table1 --c2"
    Debug.Assert strArr.Item(2) = "    (x1, x2) --c3"
End Sub

' 测试: 解析insert的rowAlias
Private Sub test_parseRowAliasEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: as
    '    测试扫描器内只有 as 关键字，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getAsToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseRowAliasEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内:  as + UNION
    '    测试扫描器内有 as 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getAsToken
    Set scanner = getObjArrayRefScanner(token, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseRowAliasEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext

    ' ==============================
    ' 3. 扫描器内:  as + 同行注释 + UNION
    '    测试扫描器内有 as 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getAsToken, createShareLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseRowAliasEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "as --c1"

    ' ==============================
    ' 4. 扫描器内:  as + 同行注释 + STR_NAME + UNION
    '    测试扫描器内有 as 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getAsToken, createShareLineCommentToken("--c1"), _
        getStrNameToken("newRow"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseRowAliasEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_AS

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "as --c1"
    Debug.Assert strArr.Item(1) = "    newRow"

    ' ==============================
    ' 5. 扫描器内:  as + 同行注释 + STR_FUNC + UNION
    '    测试扫描器内有 as 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getAsToken, createShareLineCommentToken("--c1"), _
        createFuncNameToken("newRow"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseRowAliasEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_AS

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "as --c1"
    Debug.Assert strArr.Item(1) = "    newRow"

    ' ==============================
    ' 6. 扫描器内:  as + 同行注释 + STR_FUNC + 同行注释 + UNION
    '    测试扫描器内有 as 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getAsToken, createShareLineCommentToken("--c1"), _
        createFuncNameToken("newRow"), createShareLineCommentToken("--c2"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseRowAliasEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_AS

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "as --c1"
    Debug.Assert strArr.Item(1) = "    newRow --c2"

    ' ==============================
    ' 7. 扫描器内:  as + 同行注释 + STR_FUNC 
    '            + ( c1, c2, c3 ) + 同行注释 
    '            + UNION
    '    测试扫描器内有 as 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getAsToken, createShareLineCommentToken("--c1"), _
        createFuncNameToken("newRow"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getCommaToken, getStrNameToken("c3"), _
        getRightParenthesisToken, createShareLineCommentToken("--c2"), _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseRowAliasEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_AS

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "as --c1"
    Debug.Assert strArr.Item(1) = "    newRow (c1, c2, c3) --c2"

End Sub

' 测试: 解析insert的values
Private Sub test_parseInsetValuesEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: value
    '    测试扫描器内只有 value 关键字，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getValueToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内:  value + UNION
    '    测试扫描器内有 value 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getValueToken
    Set scanner = getObjArrayRefScanner(token, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext

    ' ==============================
    ' 3. 扫描器内: value + 一个同行注释
    '    测试扫描器内有 value 关键字 + 吸收关键字后面的同行注释，并且和 value 连接到同一行
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getValueToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_VALUE

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "value --c1"

    ' ==============================
    ' 4. 扫描器内: value + 一个单行注释 + UNION
    '    测试扫描器内有 value 关键字 + 吸收关键字后面的注释 + 非表定义内容停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getValueToken
    Set scanner = getObjArrayRefScanner(token, createSingleLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c1"

    ' ==============================
    ' 5. 扫描器内: values + 一个同行注释
    '    测试扫描器内有 values 关键字 + 吸收关键字后面的同行注释，并且和 values 连接到同一行
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getValuesToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_VALUES

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "values --c1"

    ' ==============================
    ' 6. 扫描器内: values + 一个同行注释
    '           + ( 'a1', 'a2' )
    '    测试扫描器内有 values 关键字 + 吸收一个 value
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getValuesToken, createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, createStrToken("a1"), getCommaToken, createStrToken("a2"), getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_VALUES

    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "values --c1"
    Debug.Assert strArr.Item(1) = "    ("
    Debug.Assert strArr.Item(2) = "        'a1',"
    Debug.Assert strArr.Item(3) = "        'a2'"
    Debug.Assert strArr.Item(4) = "    )"

    ' ==============================
    ' 7. 扫描器内: values + 一个同行注释
    '           + ( 'a1', 'a2' )
    '           + [,] + ( 'b1', 'b2' )
    '           + union
    '    测试扫描器内有 values 关键字 + 吸收多个 value
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getValuesToken, createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, createStrToken("a1"), getCommaToken, createStrToken("a2"), getRightParenthesisToken, _
        _
        getCommaToken, _
        _
        getLeftParenthesisToken, createStrToken("b1"), getCommaToken, createStrToken("b2"), getRightParenthesisToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_VALUES

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 9
    Debug.Assert strArr.Item(0) = "values --c1"
    Debug.Assert strArr.Item(1) = "    ("
    Debug.Assert strArr.Item(2) = "        'a1',"
    Debug.Assert strArr.Item(3) = "        'a2'"
    Debug.Assert strArr.Item(4) = "    ),"
    Debug.Assert strArr.Item(5) = "    ("
    Debug.Assert strArr.Item(6) = "        'b1',"
    Debug.Assert strArr.Item(7) = "        'b2'"
    Debug.Assert strArr.Item(8) = "    )"

    ' ==============================
    ' 8. 扫描器内: values + 同行注释1 + 单行注释2
    '           + ( 'a1', 'a2' ) + 同行注释3 + 单行注释4
    '           + [,] + 同行注释5 + 单行注释6
    '           + ( 'b1', 'b2' ) + 同行注释7 + 单行注释8
    '           + [,] + 同行注释9 + 单行注释10
    '           + union
    '    测试扫描器内有 values 关键字 + 吸收多个 value + 多个注释
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getValuesToken, createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getLeftParenthesisToken, createStrToken("a1"), getCommaToken, createStrToken("a2"), getRightParenthesisToken, _
        createShareLineCommentToken("--c3"), createSingleLineCommentToken("--c4"), _
        _
        getCommaToken, createShareLineCommentToken("--c5"), createSingleLineCommentToken("--c6"), _
        _
        getLeftParenthesisToken, createStrToken("b1"), getCommaToken, createStrToken("b2"), getRightParenthesisToken, _
        createShareLineCommentToken("--c7"), createSingleLineCommentToken("--c8"), _
        _
        getCommaToken, createShareLineCommentToken("--c9"), createSingleLineCommentToken("--c10"), _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_VALUES

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c10"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 15
    Debug.Assert strArr.Item(0)  = "values --c1"
    Debug.Assert strArr.Item(1)  = "    --c2"
    Debug.Assert strArr.Item(2)  = "    ("
    Debug.Assert strArr.Item(3)  = "        'a1',"
    Debug.Assert strArr.Item(4)  = "        'a2'"
    Debug.Assert strArr.Item(5)  = "    ) --c3"
    Debug.Assert strArr.Item(6)  = "    --c4"
    Debug.Assert strArr.Item(7)  = "    , --c5"
    Debug.Assert strArr.Item(8)  = "    --c6"
    Debug.Assert strArr.Item(9)  = "    ("
    Debug.Assert strArr.Item(10) = "        'b1',"
    Debug.Assert strArr.Item(11) = "        'b2'"
    Debug.Assert strArr.Item(12) = "    ) --c7"
    Debug.Assert strArr.Item(13) = "    --c8"
    Debug.Assert strArr.Item(14) = "    , --c9"

    ' ==================================================
    '             修改配置: 列的逗号后面不需要换行
    ' ==================================================
    config.insertColListNeedLineBreakAfterComma = False

    ' ==============================
    ' 1. 扫描器内: values + 一个同行注释
    '           + ( 'a1', 'a2' )
    '           + [,] + ( 'b1', 'b2' )
    '           + union
    '    测试: 扫描器内有 values 关键字 + 吸收多个 value
    '       + 逗号后面不需要换行
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getValuesToken, createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, createStrToken("a1"), getCommaToken, createStrToken("a2"), getRightParenthesisToken, _
        _
        getCommaToken, _
        _
        getLeftParenthesisToken, createStrToken("b1"), getCommaToken, createStrToken("b2"), getRightParenthesisToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsetValuesEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_VALUES

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "values --c1"
    Debug.Assert strArr.Item(1) = "    ('a1', 'a2'),"
    Debug.Assert strArr.Item(2) = "    ('b1', 'b2')"
End Sub

' 测试: 解析赋值语句列表作用域
Private Sub test_parseAssignmentListEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: STR_NAME
    '    测试: 吸收这个 STR_NAME 作为一个不完整的赋值语句
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignmentListEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignmentListEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "x1"

    ' ==============================
    ' 2. 扫描器内: STR_NAME + UNION
    '    测试: 吸收这个 STR_NAME 作为一个不完整的赋值语句
    '         + 遇到 union 停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignmentListEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignmentListEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "x1"

    ' ==============================
    ' 3. 扫描器内:
    '    STR_NAME + [=] + STR_NAME
    '    + UNION
    '    测试: 吸收关键字后面的注释 + 吸收一个完整的赋值语句
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"), getEqConditionOperator, getStrNameToken("x2"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignmentListEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignmentListEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "x1 = x2"

    ' ==============================
    ' 4. 扫描器内:
    '    STR_NAME + [=] + STR_NAME + [,]
    '    + STR_NAME + [=] + STR_NAME
    '    + UNION
    '    测试: 吸收关键字后面的注释 + 吸收多个完整的赋值语句
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("x1"), getEqConditionOperator, getStrNameToken("x2"), _
        getCommaToken, _
        getStrNameToken("x3"), getEqConditionOperator, getStrNameToken("x4"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignmentListEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignmentListEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION


    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "x1 = x2,"
    Debug.Assert strArr.Item(1) = "x3 = x4"

    ' ==============================
    ' 5. 扫描器内:
    '      STR_NAME + 同行注释3 + 单行注释4
    '    + [=] + 同行注释5 + 单行注释6
    '    + STR_NAME + 同行注释7 + 单行注释8
    '    + [,] + 同行注释9 + 单行注释10
    '    + STR_NAME + [=] + STR_NAME + 同行注释11 + 单行注释12
    '    + [,] + 同行注释13
    '    + 单行注释14 + UNION
    '    测试扫描器内有 set 关键字 + 吸收关键字后面的注释 + 吸收多个完整的赋值语句
    '    + 各部分的注释可以被吸收 + 下一个部分前的单行注释(14) 不会被吸收
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("x1"), createShareLineCommentToken("--c3"), createSingleLineCommentToken("--c4"), _
        getEqConditionOperator, createShareLineCommentToken("--c5"), createSingleLineCommentToken("--c6"), _
        getStrNameToken("x2"), createShareLineCommentToken("--c7"), createSingleLineCommentToken("--c8"), _
        getCommaToken, createShareLineCommentToken("--c9"), createSingleLineCommentToken("--c10"), _
        _
        getStrNameToken("x3"), getEqConditionOperator, getStrNameToken("x4"), _
        createShareLineCommentToken("--c11"), createSingleLineCommentToken("--c12"), _
        _
        getCommaToken, createShareLineCommentToken("--c13"), createSingleLineCommentToken("--c14"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignmentListEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignmentListEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c14"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 11
    Debug.Assert strArr.Item(0)  = "x1 --c3"
    Debug.Assert strArr.Item(1)  = "--c4"
    Debug.Assert strArr.Item(2)  = "= --c5"
    Debug.Assert strArr.Item(3)  = "--c6"
    Debug.Assert strArr.Item(4)  = "x2 --c7"
    Debug.Assert strArr.Item(5)  = "--c8"
    Debug.Assert strArr.Item(6)  = ", --c9"
    Debug.Assert strArr.Item(7)  = "--c10"
    Debug.Assert strArr.Item(8)  = "x3 = x4 --c11"
    Debug.Assert strArr.Item(9)  = "--c12"
    Debug.Assert strArr.Item(10) = ", --c13"
End Sub

' 测试: 解析ON_DUPLICATE作用域
Private Sub test_parseInsertOnDuplicateEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: on
    '    测试扫描器内只有 on 关键字，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getOnToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: on + UNION
    '    测试: 第二个token 不是 DUPLICATE，将会返回 null
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext()
    Debug.Assert token.tokenType = SqlTokenType.KEY_ON

    ' ==============================
    ' 3. 扫描器内: on + DUPLICATE
    '    测试扫描器内只有 `ON DUPLICATE KEY UPDATE` 组合的一部分，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "on duplicate"

    ' ==============================
    ' 4. 扫描器内: on + DUPLICATE + UNION
    '    测试: 只有 `ON DUPLICATE KEY UPDATE` 的一部分，将会返回 on + DUPLICATE
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext()
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "on duplicate"

    ' ==============================
    ' 5. 扫描器内: on + DUPLICATE + KEY
    '    测试: 只有 `ON DUPLICATE KEY UPDATE` 的一部分，将会返回 on + DUPLICATE + KEY
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getKeyToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "on duplicate key"

    ' ==============================
    ' 6. 扫描器内: on + DUPLICATE + KEY + UNION
    '    测试: 只有 `ON DUPLICATE KEY UPDATE` 的一部分，将会返回 on + DUPLICATE + KEY
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getKeyToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext()
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "on duplicate key"

    ' ==============================
    ' 7. 扫描器内: on + DUPLICATE + KEY + UPDATE
    '    测试扫描器内只有 `ON DUPLICATE KEY UPDATE` 组合，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getKeyToken, getUpdateToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "on duplicate key update"

    ' ==============================
    ' 8. 扫描器内: on + DUPLICATE + KEY + UPDATE + UNION
    '    测试: 解析 `ON DUPLICATE KEY UPDATE` 组合 + 无法解析赋值语句列表
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getKeyToken, getUpdateToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext()
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "on duplicate key update"

    ' ==============================
    ' 9. 扫描器内: on + DUPLICATE + KEY + UPDATE
    '           + c1 = 'abc',
    '           + c2 = 'xyz'
    '    测试: 解析 `ON DUPLICATE KEY UPDATE` 组合 + 解析赋值语句列表
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getKeyToken, getUpdateToken, _
        getStrNameToken("c1"), getEqConditionOperator, createStrToken("abc"), getCommaToken, _
        getStrNameToken("c2"), getEqConditionOperator, createStrToken("xyz"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_DUPLICATE
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "on duplicate key update"
    Debug.Assert strArr.Item(1) = "    c1 = 'abc',"
    Debug.Assert strArr.Item(2) = "    c2 = 'xyz'"

    ' ==============================
    ' 10. 扫描器内: on + DUPLICATE + KEY + UPDATE
    '            + c1 = 'abc',
    '            + c2 = 'xyz'
    '            + UNION
    '    测试: 解析 `ON DUPLICATE KEY UPDATE` 组合 + 解析赋值语句列表
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getKeyToken, getUpdateToken, _
        getStrNameToken("c1"), getEqConditionOperator, createStrToken("abc"), getCommaToken, _
        getStrNameToken("c2"), getEqConditionOperator, createStrToken("xyz"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseInsertOnDuplicateEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_DUPLICATE
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext()
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "on duplicate key update"
    Debug.Assert strArr.Item(1) = "    c1 = 'abc',"
    Debug.Assert strArr.Item(2) = "    c2 = 'xyz'"
End Sub

' 测试: 解析 from
Private Sub test_parseFromEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: from
    '    测试扫描器内只有 from 关键字，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getFromToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseFromEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内:  from + UNION
    '    测试扫描器内有 from 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getFromToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseFromEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv

    Debug.Assert elem.getType = SqlTokenType.KEY_FROM

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert elem.toString(config) = "from"

    ' ==============================
    ' 3. 扫描器内: from + 一个同行注释
    '    测试扫描器内有 from 关键字 + 吸收关键字后面的同行注释，并且和 from 连接到同一行
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getFromToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseFromEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_FROM

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "from --c1"

    ' ==============================
    ' 4. 扫描器内: from + 一个单行注释 + UNION
    '    测试扫描器内有 from 关键字 + 吸收关键字后面的注释 + 非表定义内容停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getFromToken, createSingleLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseFromEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c1"

    Debug.Assert elem.getType = SqlTokenType.KEY_FROM

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "from"

    ' ==============================
    ' 5. 扫描器内: from + 一个同行注释 + STR_NAME
    '    测试扫描器内有 from 关键字 + 不会吸收下一个部分之前的单行注释
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getFromToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("table1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseFromEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_FROM

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "from --c1"
    Debug.Assert strArr.Item(1) = "    table1"

    ' ==============================
    ' 6. 扫描器内: from + 一个同行注释 + STR_NAME + UNION
    '    测试扫描器内有 from 关键字 + 吸收关键字后面的注释 + 吸收表定义
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getFromToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("table1"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseFromEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_FROM

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "from --c1"
    Debug.Assert strArr.Item(1) = "    table1"

    ' ==============================
    ' 7. 扫描器内: from
    '            + 一个表定义(tableName) + 一个注释 + 一个 partition
    '            + as + STR_NAME
    '    比较完整的表定义测试
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getFromToken, _
        _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseFromEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_FROM

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "from"
    Debug.Assert strArr.Item(1) = "    tableX --c1"
    Debug.Assert strArr.Item(2) = "        partition (x1, x2)"
    Debug.Assert strArr.Item(3) = "    as t1 --c2"

    ' ==============================
    ' 8. 扫描器内:
    '         + from
    '         + (
    '             + select + STR_NAME
    '             + from
    '                       + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                       + )
    '             + where
    '                   + STR_NAME + <= + STR_NAME
    '                   + and + not + exists + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                   + )
    '             + )
    '             + As + STR_NAME
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '         + [,]
    '             +  STR_NAME + as + STR_NAME
    '         + [,]
    '         + (
    '              + select + STR_NAME
    '              + from + STR_NAME
    '         + ) + STR_NAME
    '         + UNION(强制停止)
    '    测试: 只匹配到一个 select 开头的子查询 + As + 别名 + (col_list)
    '         + 子查询中也包含子查询 + 查询条件中也包含子查询
    '         + 两个表定义是子查询 + 中间一个纯名称的表定义
    ' ==============================
    ' 1. 创建扫描器
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(100)
    '    添加筛选项
    addElemToArray meanTokens, _
        getFromToken

    addElemToArray meanTokens, _
        getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), getFromToken, _
        _
        getLeftParenthesisToken, getSelectToken, getStrNameToken("b1"), getFromToken, getStrNameToken("table2"), getRightParenthesisToken, _
        _
        getWhereToken, getStrNameToken("q1"), getLeConditionOperator, getStrNameToken("q2"), _
        _
        getAndToken, getNotToken, getExistsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("e1"), getFromToken, getStrNameToken("subTable"), getRightParenthesisToken, _
        _
        getAndToken, getStrNameToken("q3"), getLeConditionOperator, getStrNameToken("q4"), _
        _
        getRightParenthesisToken, getAsToken, getStrNameToken("xyz"), _
        _
        getLeftParenthesisToken, getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getCommaToken, getStrNameToken("c3"), getRightParenthesisToken

    addElemToArray meanTokens, _
        getCommaToken

    addElemToArray meanTokens, _
        getStrNameToken("tableX"), getAsToken, getStrNameToken("tx")

    addElemToArray meanTokens, _
        getCommaToken

    addElemToArray meanTokens, _
        getLeftParenthesisToken, getSelectToken, getStrNameToken("m1"), getFromToken, getStrNameToken("m2"), getRightParenthesisToken, _
        getStrNameToken("tn")

    addElemToArray meanTokens, _
        getUnionToken

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 执行测试方法
    Set elem = analyzer.parseFromEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 28
    Debug.Assert strArr.Item(0)  = "from"
    Debug.Assert strArr.Item(1)  = "    ("
    Debug.Assert strArr.Item(2)  = "        select"
    Debug.Assert strArr.Item(3)  = "            a2"
    Debug.Assert strArr.Item(4)  = "        from"
    Debug.Assert strArr.Item(5)  = "            ("
    Debug.Assert strArr.Item(6)  = "                select"
    Debug.Assert strArr.Item(7)  = "                    b1"
    Debug.Assert strArr.Item(8)  = "                from"
    Debug.Assert strArr.Item(9)  = "                    table2"
    Debug.Assert strArr.Item(10)  = "            )"
    Debug.Assert strArr.Item(11) = "        where"
    Debug.Assert strArr.Item(12) = "            q1 <= q2"
    Debug.Assert strArr.Item(13) = "            and not exists ("
    Debug.Assert strArr.Item(14) = "                    select"
    Debug.Assert strArr.Item(15) = "                        e1"
    Debug.Assert strArr.Item(16) = "                    from"
    Debug.Assert strArr.Item(17) = "                        subTable"
    Debug.Assert strArr.Item(18) = "                )"
    Debug.Assert strArr.Item(19) = "            and q3 <= q4"
    Debug.Assert strArr.Item(20) = "    ) as xyz (c1, c2, c3),"
    Debug.Assert strArr.Item(21) = "    tableX as tx,"
    Debug.Assert strArr.Item(22) = "    ("
    Debug.Assert strArr.Item(23) = "        select"
    Debug.Assert strArr.Item(24) = "            m1"
    Debug.Assert strArr.Item(25) = "        from"
    Debug.Assert strArr.Item(26) = "            m2"
    Debug.Assert strArr.Item(27) = "    ) tn"
End Sub

' 测试: 解析 join
Private Sub test_parseJoinEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim token As SqlToken
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: join
    '    测试扫描器内只有 join 关键字，并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getJoinToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseJoinEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: join + UNION
    '    测试扫描器内有 join 关键字 + 后面没有表定义并停止
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getJoinToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseJoinEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv

    Debug.Assert elem.getType = SqlTokenType.KEY_JOIN

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert elem.toString(config) = "join"

    ' ==============================
    ' 3. 扫描器内: join + 一个同行注释
    '    测试扫描器内有 join 关键字 + 吸收关键字后面的同行注释并连接到 join 的后面
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getJoinToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseJoinEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_JOIN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "join --c1"

    ' ==============================
    ' 4. 扫描器内: join + 一个单行注释 + UNION
    '    测试扫描器内有 join 关键字 + 不会吸收下一个部分之前的单行注释
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getJoinToken, createSingleLineCommentToken("--c1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseJoinEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c1"

    Debug.Assert elem.getType = SqlTokenType.KEY_JOIN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 1
    Debug.Assert strArr.Item(0) = "join"

    ' ==============================
    ' 5. 扫描器内: join + 一个同行注释 + STR_NAME
    '    测试扫描器内有 join 关键字 + 吸收关键字后面的注释 + 吸收表定义
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getJoinToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("table1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseJoinEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Debug.Assert elem.getType = SqlTokenType.KEY_JOIN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "join --c1"
    Debug.Assert strArr.Item(1) = "    table1"

    ' ==============================
    ' 6. 扫描器内: join + 一个注释 + STR_NAME + UNION
    '    测试扫描器内有 join 关键字 + 吸收关键字后面的注释 + 吸收表定义
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getJoinToken, _
        createShareLineCommentToken("--c1"), getStrNameToken("table1"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseJoinEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Debug.Assert elem.getType = SqlTokenType.KEY_JOIN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "join --c1"
    Debug.Assert strArr.Item(1) = "    table1"

    ' ==============================
    ' 7. 扫描器内: join
    '            + 一个表定义(tableName) + 一个注释 + 一个 partition
    '            + as + STR_NAME
    '    比较完整的表定义测试
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getJoinToken, _
        _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseJoinEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert elem.getType = SqlTokenType.KEY_JOIN

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "join"
    Debug.Assert strArr.Item(1) = "    tableX --c1"
    Debug.Assert strArr.Item(2) = "        partition (x1, x2)"
    Debug.Assert strArr.Item(3) = "    as t1 --c2"

    ' ==============================
    ' 8. 扫描器内:
    '         + left + join
    '         + (
    '             + select + STR_NAME
    '             + from
    '                       + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                       + )
    '             + where
    '                   + STR_NAME + <= + STR_NAME
    '                   + and + not + exists + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                   + )
    '             + )
    '             + As + STR_NAME
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '         + [,]
    '             +  STR_NAME + as + STR_NAME
    '         + [,]
    '         + (
    '              + select + STR_NAME
    '              + from + STR_NAME
    '         + ) + STR_NAME
    '         + UNION(强制停止)
    '    测试: 只匹配到一个 select 开头的子查询 + As + 别名 + (col_list)
    '         + 子查询中也包含子查询 + 查询条件中也包含子查询
    '         + 两个表定义是子查询 + 中间一个纯名称的表定义
    ' ==============================
    ' 1. 创建扫描器
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(100)
    '    添加筛选项
    addElemToArray meanTokens, _
        getLeftToken, getJoinToken

    addElemToArray meanTokens, _
        getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), getFromToken, _
        _
        getLeftParenthesisToken, getSelectToken, getStrNameToken("b1"), getFromToken, getStrNameToken("table2"), getRightParenthesisToken, _
        _
        getWhereToken, getStrNameToken("q1"), getLeConditionOperator, getStrNameToken("q2"), _
        _
        getAndToken, getNotToken, getExistsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("e1"), getFromToken, getStrNameToken("subTable"), getRightParenthesisToken, _
        _
        getAndToken, getStrNameToken("q3"), getLeConditionOperator, getStrNameToken("q4"), _
        _
        getRightParenthesisToken, getAsToken, getStrNameToken("xyz"), _
        _
        getLeftParenthesisToken, getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getCommaToken, getStrNameToken("c3"), getRightParenthesisToken

    addElemToArray meanTokens, _
        getUnionToken

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 执行测试方法
    Set elem = analyzer.parseJoinEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 21
    Debug.Assert strArr.Item(0)  = "left join"
    Debug.Assert strArr.Item(1)  = "    ("
    Debug.Assert strArr.Item(2)  = "        select"
    Debug.Assert strArr.Item(3)  = "            a2"
    Debug.Assert strArr.Item(4)  = "        from"
    Debug.Assert strArr.Item(5)  = "            ("
    Debug.Assert strArr.Item(6)  = "                select"
    Debug.Assert strArr.Item(7)  = "                    b1"
    Debug.Assert strArr.Item(8)  = "                from"
    Debug.Assert strArr.Item(9)  = "                    table2"
    Debug.Assert strArr.Item(10) = "            )"
    Debug.Assert strArr.Item(11) = "        where"
    Debug.Assert strArr.Item(12) = "            q1 <= q2"
    Debug.Assert strArr.Item(13) = "            and not exists ("
    Debug.Assert strArr.Item(14) = "                    select"
    Debug.Assert strArr.Item(15) = "                        e1"
    Debug.Assert strArr.Item(16) = "                    from"
    Debug.Assert strArr.Item(17) = "                        subTable"
    Debug.Assert strArr.Item(18) = "                )"
    Debug.Assert strArr.Item(19) = "            and q3 <= q4"
    Debug.Assert strArr.Item(20) = "    ) as xyz (c1, c2, c3)"
End Sub

' [left[outer]|right[outer]|inner|cross] join
' 测试: 解析join关键字
Private Sub test_parseJoinKey()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim scanner As ObjArrayRefScanner
    Dim joinKey As ISqlFormatterElem
    Dim token As SqlToken

    ' ==============================
    ' 1. 默认情况下，无论第一个是什么元素都会被取出的
    ' ==============================
    ' 1 创建扫描器: 设置一个左括号 + 一个个右括号
    Set scanner = getObjArrayRefScanner( _
        getLeftParenthesisToken, _
        getRightParenthesisToken _
    )

    ' 2. 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)

    ' 3. 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "("
    '     检查: 扫描器没有用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 2. join
    ' ==============================
    ' 1. 扫描器内: 只有 join
    ' 1.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getJoinToken _
    )

    ' 1.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)

    ' 1.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "join"
    '     检查: 扫描器已经用完
    Debug.Assert scanner.hasNext = False

    ' 2. 扫描器内:  join + 右括号
    ' 2.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getJoinToken, _
        getRightParenthesisToken _
    )

    ' 2.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 2.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "join"
    '     检查: 扫描器没有用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 3. left join
    ' ==============================
    ' 1. 扫描器内: 只有 left + 右括号
    ' 1.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftToken, _
        getRightParenthesisToken _
    )

    ' 1.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)

    ' 1.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "left"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' 2. 扫描器内:  left join
    ' 2.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftToken, _
        getJoinToken _
    )

    ' 2.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 2.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "left join"
    '     检查: 扫描器用完
    Debug.Assert scanner.hasNext = False

    ' 3. 扫描器内:  left join + 右括号
    ' 3.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftToken, _
        getJoinToken, _
        getRightParenthesisToken _
    )

    ' 3.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 3.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "left join"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 4. left outer join
    ' ==============================
    ' 1. 扫描器内: 只有 left + outer
    ' 1.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftToken, _
        getOuterToken _
    )

    ' 1.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)

    ' 1.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "left outer"
    '     检查: 扫描器用完
    Debug.Assert scanner.hasNext = False

    ' 2. 扫描器内:  left outer + join
    ' 2.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftToken, _
        getOuterToken, _
        getJoinToken _
    )

    ' 2.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 2.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "left outer join"
    '     检查: 扫描器用完
    Debug.Assert scanner.hasNext = False

    ' 3. 扫描器内:  left outer join + 右括号
    ' 3.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getLeftToken, _
        getOuterToken, _
        getJoinToken, _
        getRightParenthesisToken _
    )

    ' 3.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 3.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "left outer join"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 5. right join
    ' ==============================
    ' 1. 扫描器内: 只有 right + 右括号
    ' 1.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getRightToken, _
        getRightParenthesisToken _
    )

    ' 1.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)

    ' 1.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "right"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' 2. 扫描器内:  right join
    ' 2.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getRightToken, _
        getJoinToken _
    )

    ' 2.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 2.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "right join"
    '     检查: 扫描器用完
    Debug.Assert scanner.hasNext = False

    ' 3. 扫描器内:  right join + 右括号
    ' 3.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getRightToken, _
        getJoinToken, _
        getRightParenthesisToken _
    )

    ' 3.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 3.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "right join"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 6. right outer join
    ' ==============================
    ' 1. 扫描器内: 只有 right + outer
    ' 1.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getRightToken, _
        getOuterToken _
    )

    ' 1.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)

    ' 1.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "right outer"
    '     检查: 扫描器用完
    Debug.Assert scanner.hasNext = False

    ' 2. 扫描器内:  right outer + join
    ' 2.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getRightToken, _
        getOuterToken, _
        getJoinToken _
    )

    ' 2.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 2.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "right outer join"
    '     检查: 扫描器用完
    Debug.Assert scanner.hasNext = False

    ' 3. 扫描器内:  right outer join + 右括号
    ' 3.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getRightToken, _
        getOuterToken, _
        getJoinToken, _
        getRightParenthesisToken _
    )

    ' 3.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 3.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "right outer join"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 7. inner join
    ' ==============================
    ' 1. 扫描器内: 只有 inner + 右括号
    ' 1.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getInnerToken, _
        getRightParenthesisToken _
    )

    ' 1.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)

    ' 1.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "inner"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' 2. 扫描器内:  inner join
    ' 2.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getInnerToken, _
        getJoinToken _
    )

    ' 2.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 2.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "inner join"
    '     检查: 扫描器用完
    Debug.Assert scanner.hasNext = False

    ' 3. 扫描器内:  inner join + 右括号
    ' 3.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getInnerToken, _
        getJoinToken, _
        getRightParenthesisToken _
    )

    ' 3.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 3.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "inner join"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' ==============================
    ' 8. cross join
    ' ==============================
    ' 1. 扫描器内: 只有 inner + 右括号
    ' 1.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getCrossToken, _
        getRightParenthesisToken _
    )

    ' 1.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)

    ' 1.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "cross"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

    ' 2. 扫描器内:  cross join
    ' 2.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getCrossToken, _
        getJoinToken _
    )

    ' 2.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 2.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "cross join"
    '     检查: 扫描器用完
    Debug.Assert scanner.hasNext = False

    ' 3. 扫描器内:  cross join + 右括号
    ' 3.1 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getCrossToken, _
        getJoinToken, _
        getRightParenthesisToken _
    )

    ' 3.2 执行解析
    Set joinKey = analyzer.parseJoinKey(scanner)
    ' 3.3 检查结果
    '     检查生成结果
    Debug.Assert joinKey.toString(config) = "cross join"
    '     检查: 扫描器没用完，并且下一个是右括号
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = ")"

End Sub

' 测试: 解析连续出现的多个表定义
Private Sub test_parseTableReferences()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim meanTokens As IObjArrayRef

    ' ==============================
    ' 1. 扫描器内: 空
    '    测试空扫描器
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)
    scanner.toNext
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: UNION
    '    测试无法解析出一个表定义
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 3. 扫描器内: 一个表定义(tableName)
    '    测试只有一个表定义 + 解析一个表定义后，扫描器用完
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getStrNameToken("table1")
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 4. 扫描器内: 一个表定义(tableName) + 一个注释
    '    测试只有一个表定义 + 解析一个表定义后，遇到注释 并吸收
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("table1"), createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "table1 --c1"

    ' ==============================
    ' 5. 扫描器内: 一个表定义(tableName) + 一个注释 + [,]
    '    测试只有一个表定义 + 解析一个表定义后，遇到注释 + 逗号 并吸收
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("table1"), createShareLineCommentToken("--c1"), _
        getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "table1 --c1"
    Debug.Assert strArr.Item(1) = ","

    ' ==============================
    ' 6. 扫描器内: 一个表定义(tableName) + 一个注释 + [,] + 一个表定义(tableName)
    '    测试有多个表定义 + 解析一个表定义后，遇到注释 + 逗号 并吸收
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("table1"), createShareLineCommentToken("--c1"), _
        getCommaToken, _
        getStrNameToken("table2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "table1 --c1"
    Debug.Assert strArr.Item(1) = ","
    Debug.Assert strArr.Item(2) = "table2"

    ' ==============================
    ' 7. 扫描器内:
    '         + (
    '             + select + STR_NAME
    '             + from
    '                       + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                       + )
    '             + where
    '                   + STR_NAME + <= + STR_NAME
    '                   + and + not + exists + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                   + )
    '             + )
    '             + As + STR_NAME
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '         + [,]
    '             +  STR_NAME + as + STR_NAME
    '         + [,]
    '         + (
    '              + select + STR_NAME
    '              + from + STR_NAME
    '         + ) + STR_NAME
    '         + UNION(强制停止)
    '    测试: 只匹配到一个 select 开头的子查询 + As + 别名 + (col_list)
    '         + 子查询中也包含子查询 + 查询条件中也包含子查询
    '         + 两个表定义是子查询 + 中间一个纯名称的表定义
    ' ==============================
    ' 1. 创建扫描器
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(100)
    '    添加筛选项
    addElemToArray meanTokens, _
        getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), getFromToken, _
        _
        getLeftParenthesisToken, getSelectToken, getStrNameToken("b1"), getFromToken, getStrNameToken("table2"), getRightParenthesisToken, _
        _
        getWhereToken, getStrNameToken("q1"), getLeConditionOperator, getStrNameToken("q2"), _
        _
        getAndToken, getNotToken, getExistsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("e1"), getFromToken, getStrNameToken("subTable"), getRightParenthesisToken, _
        _
        getAndToken, getStrNameToken("q3"), getLeConditionOperator, getStrNameToken("q4"), _
        _
        getRightParenthesisToken, getAsToken, getStrNameToken("xyz"), _
        _
        getLeftParenthesisToken, getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getCommaToken, getStrNameToken("c3"), getRightParenthesisToken

    addElemToArray meanTokens, _
        getCommaToken

    addElemToArray meanTokens, _
        getStrNameToken("tableX"), getAsToken, getStrNameToken("tx")

    addElemToArray meanTokens, _
        getCommaToken

    addElemToArray meanTokens, _
        getLeftParenthesisToken, getSelectToken, getStrNameToken("m1"), getFromToken, getStrNameToken("m2"), getRightParenthesisToken, _
        getStrNameToken("tn")

    addElemToArray meanTokens, _
        getUnionToken

    ' 初始化扫描器
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 27
    Debug.Assert strArr.Item(0)  = "("
    Debug.Assert strArr.Item(1)  = "    select"
    Debug.Assert strArr.Item(2)  = "        a2"
    Debug.Assert strArr.Item(3)  = "    from"
    Debug.Assert strArr.Item(4)  = "        ("
    Debug.Assert strArr.Item(5)  = "            select"
    Debug.Assert strArr.Item(6)  = "                b1"
    Debug.Assert strArr.Item(7)  = "            from"
    Debug.Assert strArr.Item(8)  = "                table2"
    Debug.Assert strArr.Item(9)  = "        )"
    Debug.Assert strArr.Item(10) = "    where"
    Debug.Assert strArr.Item(11) = "        q1 <= q2"
    Debug.Assert strArr.Item(12) = "        and not exists ("
    Debug.Assert strArr.Item(13) = "                select"
    Debug.Assert strArr.Item(14) = "                    e1"
    Debug.Assert strArr.Item(15) = "                from"
    Debug.Assert strArr.Item(16) = "                    subTable"
    Debug.Assert strArr.Item(17) = "            )"
    Debug.Assert strArr.Item(18) = "        and q3 <= q4"
    Debug.Assert strArr.Item(19) = ") as xyz (c1, c2, c3),"
    Debug.Assert strArr.Item(20) = "tableX as tx,"
    Debug.Assert strArr.Item(21) = "("
    Debug.Assert strArr.Item(22) = "    select"
    Debug.Assert strArr.Item(23) = "        m1"
    Debug.Assert strArr.Item(24) = "    from"
    Debug.Assert strArr.Item(25) = "        m2"
    Debug.Assert strArr.Item(26) = ") tn"

    ' ==============================
    ' 7. 扫描器内: tableName + 一个同行注释1
    '            + 一个单行注释2 + [,] + 一个同行注释3
    '            + 一个单行注释4 + tableName + 一个同行注释5
    '            + 一个单行注释6 + union
    '    测试: 多个表定义之间的注释可以被吸收 + 下一个部分之前的注释不会被吸收 + 末尾的同行注释会被吸收
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("table1"), createShareLineCommentToken("--c1"), _
        createSingleLineCommentToken("--c2"), getCommaToken, createShareLineCommentToken("--c3"), _
        createSingleLineCommentToken("--c4"), getStrNameToken("table2"), createShareLineCommentToken("--c5"), _
        createSingleLineCommentToken("--c6"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c6"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "table1 --c1"
    Debug.Assert strArr.Item(1) = "--c2"
    Debug.Assert strArr.Item(2) = ", --c3"
    Debug.Assert strArr.Item(3) = "--c4"
    Debug.Assert strArr.Item(4) = "table2 --c5"

    ' ==============================
    ' 8. 扫描器内: 
    '          STR_NAME + [.] + [*]
    '          + [,]
    '          + STR_NAME + [.] + [*]
    '    测试兼容解析 delete 中的 tbl_name.*，并且是连续出现的多个 tbl_name.*
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("table1"), getPointToken, getAsteriskToken, _
        getCommaToken, _
        getStrNameToken("table2"), getPointToken, getAsteriskToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableReferences(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "table1.*,"
    Debug.Assert strArr.Item(1) = "table2.*"
End Sub

' 测试: 匹配一个表定义
Private Sub test_parseTableFactor()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: 一个表定义(tableName) + 一个注释 + 一个 partition
    '            + as + STR_NAME + 一个注释 + 一个 index_hint_list + UNION (非partition/as/别名 STR_NAME/index_hint_list)
    '    测试以 STR_NAME 开头的解析
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"), _
        _
        getUseToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        getCommaToken, _
        getForceToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x3"), getCommaToken, getStrNameToken("x4"), getRightParenthesisToken, _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactor(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 7
    Debug.Assert strArr.Item(0) = "tableX --c1"
    Debug.Assert strArr.Item(1) = "    partition (x1, x2)"
    Debug.Assert strArr.Item(2) = "as t1 --c2"
    Debug.Assert strArr.Item(3) = "    use key"
    Debug.Assert strArr.Item(4) = "        for group by (x1, x2),"
    Debug.Assert strArr.Item(5) = "    force key"
    Debug.Assert strArr.Item(6) = "        for group by (x3, x4)"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 2. 扫描器内: (
    '             + select + STR_NAME
    '             + from
    '                       + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                       + )
    '             + where
    '                   + STR_NAME + <= + STR_NAME
    '             + )
    '             + As + STR_NAME
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '             + UNION(强制停止)
    '    测试: 匹配左括号开头的子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), _
        getFromToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("b1"), _
        getFromToken, getStrNameToken("table2"), _
        getRightParenthesisToken, _
        _
        getWhereToken, getStrNameToken("q1"), getLeConditionOperator, getStrNameToken("q2"), _
        getRightParenthesisToken, _
        _
        getAsToken, getStrNameToken("xyz"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getCommaToken, getStrNameToken("c3"), _
        getRightParenthesisToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactor(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 13
    Debug.Assert strArr.Item(0)  = "("
    Debug.Assert strArr.Item(1)  = "    select"
    Debug.Assert strArr.Item(2)  = "        a2"
    Debug.Assert strArr.Item(3)  = "    from"
    Debug.Assert strArr.Item(4)  = "        ("
    Debug.Assert strArr.Item(5)  = "            select"
    Debug.Assert strArr.Item(6)  = "                b1"
    Debug.Assert strArr.Item(7)  = "            from"
    Debug.Assert strArr.Item(8)  = "                table2"
    Debug.Assert strArr.Item(9)  = "        )"
    Debug.Assert strArr.Item(10) = "    where"
    Debug.Assert strArr.Item(11) = "        q1 <= q2"
    Debug.Assert strArr.Item(12) = ") as xyz (c1, c2, c3)"
End Sub

' 测试: 匹配一个表名称定义开始的表定义
Private Sub test_parseTableFactorFromTableName()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: 一个表定义(tableName)
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getStrNameToken("tableX")
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: 一个表定义(tableName) + UNION (非partition/as/别名 STR_NAME/index_hint_list)
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getStrNameToken("tableX")
    Set scanner = getObjArrayRefScanner(token, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 3. 扫描器内: 一个表定义(tableName) + as
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("tableX"), getAsToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "tableX as"

    ' ==============================
    ' 4. 扫描器内: 一个表定义(tableName) + as + UNION (非partition/别名 STR_NAME/index_hint_list)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("tableX"), getAsToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert elem.toString(config) = "tableX as"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 5. 扫描器内: 一个表定义(tableName) + 一个别名 STR_NAME
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("tableX"), getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "tableX x1"

    ' ==============================
    ' 6. 扫描器内: 一个表定义(tableName) + 一个别名 STR_NAME + UNION (非partition/as/index_hint_list)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("tableX"), getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert elem.toString(config) = "tableX x1"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 7. 扫描器内: 一个表定义(tableName) 一个注释 + 一个 partition + UNION (非partition/as/别名 STR_NAME/index_hint_list)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "tableX --c1"
    Debug.Assert strArr.Item(1) = "    partition (x1, x2)"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 8. 扫描器内: 一个表定义(tableName) 一个注释 + 一个 index_hint_list + UNION (非partition/as/别名 STR_NAME/index_hint_list)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getUseToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        getCommaToken, _
        getForceToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x3"), getCommaToken, getStrNameToken("x4"), getRightParenthesisToken, _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 5
    Debug.Assert strArr.Item(0) = "tableX --c1"
    Debug.Assert strArr.Item(1) = "    use key"
    Debug.Assert strArr.Item(2) = "        for group by (x1, x2),"
    Debug.Assert strArr.Item(3) = "    force key"
    Debug.Assert strArr.Item(4) = "        for group by (x3, x4)"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 9. 扫描器内: 一个表定义(tableName) 一个注释 + 一个 partition
    '            + as + STR_NAME + 一个注释 + 一个 index_hint_list + UNION (非partition/as/别名 STR_NAME/index_hint_list)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"), _
        _
        getUseToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        getCommaToken, _
        getForceToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x3"), getCommaToken, getStrNameToken("x4"), getRightParenthesisToken, _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 7
    Debug.Assert strArr.Item(0) = "tableX --c1"
    Debug.Assert strArr.Item(1) = "    partition (x1, x2)"
    Debug.Assert strArr.Item(2) = "as t1 --c2"
    Debug.Assert strArr.Item(3) = "    use key"
    Debug.Assert strArr.Item(4) = "        for group by (x1, x2),"
    Debug.Assert strArr.Item(5) = "    force key"
    Debug.Assert strArr.Item(6) = "        for group by (x3, x4)"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 10. 扫描器内: 一个表定义(tableName) + 一个别名 STR_NAME + 一个同行注释 + 一个单行注释
    '     测试: 末尾的同行注释可以被吸收，单行注释不会被吸收
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("tableX"), getStrNameToken("x1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromTableName(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c2"

    Debug.Assert elem.toString(config) = "tableX x1 --c1"
End Sub

' 测试: 匹配一个子查询开始的表定义
Private Sub test_parseTableFactorFromSubQuery()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: 一个 UNION
    '    测试: 无法解析出一个子查询时，返回 null
    ' ==============================
    ' 1. 创建扫描器
    Set token = getUnionToken
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromSubQuery(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Debug.Assert scanner.getNext Is token

    ' ==============================
    ' 2. 扫描器内: (
    '            + with
    '                 + STR_FUN
    '                 + ( + STR_NAME + [,] + STR_NAME + )
    '                 + As
    '                 + (
    '                      + select + STR_NAME
    '                      + from + STR_NAME
    '                 + )
    '             + select + STR_NAME
    '             + from + STR_NAME
    '             + )
    '    测试: 只匹配到一个 with 开头的子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        getWithToken, createFuncNameToken("t1"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("x1"), _
        getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("a1"), _
        getFromToken, getStrNameToken("table1"), _
        getRightParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), _
        getFromToken, getStrNameToken("table2"), _
        _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromSubQuery(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 12
    Debug.Assert strArr.Item(0) =  "("
    Debug.Assert strArr.Item(1) =  "    with t1 (x1, x2) as ("
    Debug.Assert strArr.Item(2) =  "        select"
    Debug.Assert strArr.Item(3) =  "            a1"
    Debug.Assert strArr.Item(4) =  "        from"
    Debug.Assert strArr.Item(5) =  "            table1"
    Debug.Assert strArr.Item(6) =  "    )"
    Debug.Assert strArr.Item(7) =  "    select"
    Debug.Assert strArr.Item(8) =  "        a2"
    Debug.Assert strArr.Item(9) =  "    from"
    Debug.Assert strArr.Item(10) = "        table2"
    Debug.Assert strArr.Item(11) = ")"

    ' ==============================
    ' 3. 扫描器内: (
    '             + select + STR_NAME
    '             + from + STR_NAME
    '             + )
    '    测试: 只匹配到一个 select 开头的子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), _
        getFromToken, getStrNameToken("table2"), _
        _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromSubQuery(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) =  "("
    Debug.Assert strArr.Item(1) =  "    select"
    Debug.Assert strArr.Item(2) =  "        a2"
    Debug.Assert strArr.Item(3) =  "    from"
    Debug.Assert strArr.Item(4) = "        table2"
    Debug.Assert strArr.Item(5) = ")"

    ' ==============================
    ' 4. 扫描器内: (
    '             + select + STR_NAME
    '             + from + STR_NAME
    '             + )
    '             + As
    '    测试: 只匹配到一个 select 开头的子查询 + As 结束
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), _
        getFromToken, getStrNameToken("table2"), _
        _
        getRightParenthesisToken, _
        getAsToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromSubQuery(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "("
    Debug.Assert strArr.Item(1) = "    select"
    Debug.Assert strArr.Item(2) = "        a2"
    Debug.Assert strArr.Item(3) = "    from"
    Debug.Assert strArr.Item(4) = "        table2"
    Debug.Assert strArr.Item(5) = ") as"

    ' ==============================
    ' 5. 扫描器内: (
    '             + select + STR_NAME
    '             + from + STR_NAME
    '             + )
    '             + As + STR_NAME
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '    测试: 只匹配到一个 select 开头的子查询 + As + 别名 + (col_list)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), _
        getFromToken, getStrNameToken("table2"), _
        _
        getRightParenthesisToken, _
        getAsToken, getStrNameToken("xyz"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getCommaToken, getStrNameToken("c3"), _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromSubQuery(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "("
    Debug.Assert strArr.Item(1) = "    select"
    Debug.Assert strArr.Item(2) = "        a2"
    Debug.Assert strArr.Item(3) = "    from"
    Debug.Assert strArr.Item(4) = "        table2"
    Debug.Assert strArr.Item(5) = ") as xyz (c1, c2, c3)"

    ' ==============================
    ' 6. 扫描器内: (
    '             + select + STR_NAME
    '             + from + STR_NAME
    '             + )
    '             + As + STR_NAME
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '    测试: 只匹配到一个 select 开头的子查询 + As + 别名
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), _
        getFromToken, getStrNameToken("table2"), _
        _
        getRightParenthesisToken, _
        getAsToken, getStrNameToken("xyz"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromSubQuery(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "("
    Debug.Assert strArr.Item(1) = "    select"
    Debug.Assert strArr.Item(2) = "        a2"
    Debug.Assert strArr.Item(3) = "    from"
    Debug.Assert strArr.Item(4) = "        table2"
    Debug.Assert strArr.Item(5) = ") as xyz"

    ' ==============================
    ' 7. 扫描器内: (
    '             + select + STR_NAME
    '             + from
    '                       + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                       + )
    '             + where
    '                   + STR_NAME + <= + STR_NAME
    '                   + and + not + exists + (
    '                       + select + STR_NAME
    '                       + from + STR_NAME
    '                   + )
    '             + )
    '             + As + STR_NAME
    '             + ( + STR_NAME + [,] + STR_NAME + [,] + STR_NAME + )
    '    测试: 只匹配到一个 select 开头的子查询 + As + 别名 + (col_list)
    '         + 子查询中也包含子查询 + 查询条件中也包含子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLeftParenthesisToken, _
        _
        getSelectToken, getStrNameToken("a2"), _
        getFromToken, _
        _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("b1"), _
        getFromToken, getStrNameToken("table2"), _
        getRightParenthesisToken, _
        _
        getWhereToken, getStrNameToken("q1"), getLeConditionOperator, getStrNameToken("q2"), _
        _
        getAndToken, getNotToken, getExistsToken, getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("e1"), getFromToken, getStrNameToken("subTable"), _
        getRightParenthesisToken, _
        _
        getAndToken, getStrNameToken("q3"), getLeConditionOperator, getStrNameToken("q4"), _
        _
        getRightParenthesisToken, _
        getAsToken, getStrNameToken("xyz"), _
        _
        getLeftParenthesisToken, _
        getStrNameToken("c1"), getCommaToken, getStrNameToken("c2"), getCommaToken, getStrNameToken("c3"), _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseTableFactorFromSubQuery(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTableFactorEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 20
    Debug.Assert strArr.Item(0)  = "("
    Debug.Assert strArr.Item(1)  = "    select"
    Debug.Assert strArr.Item(2)  = "        a2"
    Debug.Assert strArr.Item(3)  = "    from"
    Debug.Assert strArr.Item(4)  = "        ("
    Debug.Assert strArr.Item(5)  = "            select"
    Debug.Assert strArr.Item(6)  = "                b1"
    Debug.Assert strArr.Item(7)  = "            from"
    Debug.Assert strArr.Item(8)  = "                table2"
    Debug.Assert strArr.Item(9)  = "        )"
    Debug.Assert strArr.Item(10) = "    where"
    Debug.Assert strArr.Item(11) = "        q1 <= q2"
    Debug.Assert strArr.Item(12) = "        and not exists ("
    Debug.Assert strArr.Item(13) = "                select"
    Debug.Assert strArr.Item(14) = "                    e1"
    Debug.Assert strArr.Item(15) = "                from"
    Debug.Assert strArr.Item(16) = "                    subTable"
    Debug.Assert strArr.Item(17) = "            )"
    Debug.Assert strArr.Item(18) = "        and q3 <= q4"
    Debug.Assert strArr.Item(19) = ") as xyz (c1, c2, c3)"

End Sub

' 测试: 匹配一个表名称定义
Private Sub test_parseTableName()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem

    ' ==============================
    ' 1. 扫描器中只有一个 STR_NAME
    ' ==============================
    Set token = getStrNameToken("t1")

    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableName(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: STR_NAME + 一个非 [.] 元素
    ' ==============================
    Set token = getStrNameToken("t1")

    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, getStrNameToken("xx"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableName(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "xx"

    ' ==============================
    ' 3. 扫描器内: STR_NAME + [.]
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("t1"), _
        getPointToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableName(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContinuousStrElem
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "t1."

    ' ==============================
    ' 4. 扫描器内: STR_NAME + [.] + 一个非 STR_NAME
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("t1"), _
        getPointToken, _
        getLeftParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableName(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContinuousStrElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS
    Debug.Assert elem.toString(config) = "t1."

    ' ==============================
    ' 5. 扫描器内: STR_NAME + [.] + STR_NAME
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("t1"), _
        getPointToken, _
        getStrNameToken("xx"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableName(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContinuousStrElem
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "t1.xx"

    ' ==============================
    ' 6. 扫描器内: STR_NAME + [.] + STR_NAME + 一个其他元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("t1"), _
        getPointToken, _
        getStrNameToken("xx"), _
        getLeftParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableName(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContinuousStrElem
    Debug.Assert elem.toString(config) = "t1.xx"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    ' ==============================
    ' 7. 扫描器内: STR_NAME + [.] + [*]
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("t1"), _
        getPointToken, _
        getAsteriskToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseTableName(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContinuousStrElem
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "t1.*"
End Sub

' 测试: 匹配 partition
Private Sub test_parsePartition()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: partition
    ' ==============================
    ' 1. 创建一个扫描器
    Set token = getPartitionToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: partition + 一个非注释/非左括号的元素
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "partition"

    ' ==============================
    ' 3. 扫描器内: partition + 一个注释
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "partition --c1"

    ' ==============================
    ' 4. 扫描器内: partition + 一个注释 + (
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "partition --c1"
    Debug.Assert strArr.Item(1) = "("

    ' ==============================
    ' 5. 扫描器内: partition + ( + UNION(强制停止)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, getLeftParenthesisToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "partition ("

    ' ==============================
    ' 6. 扫描器内: partition + ( + STR_NAME + UNION(强制停止)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "partition (x1"

    ' ==============================
    ' 7. 扫描器内: partition + ( + STR_NAME + [,] + UNION(强制停止)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "partition (x1,"

    ' ==============================
    ' 8. 扫描器内: partition + ( + STR_NAME + [,] + 一个注释 + UNION(强制停止)
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, createShareLineCommentToken("--c1"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "partition (x1, --c1"

    ' ==============================
    ' 9. 扫描器内: partition + ( + STR_NAME + [,] + 一个注释 + )
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, createShareLineCommentToken("--c1"), _
        getRightParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "partition (x1, --c1"
    Debug.Assert strArr.Item(1) = ")"

    ' ==============================
    ' 10. 扫描器内: partition + ( + STR_NAME + [,] + 一个注释 + )
    '              + UNION
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, createShareLineCommentToken("--c1"), _
        getRightParenthesisToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parsePartition(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "partition (x1, --c1"
    Debug.Assert strArr.Item(1) = ")"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

End Sub

' 测试: 匹配 index hint list
Private Sub test_parseIndexHintList()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: index_hint
    ' ==============================
    ' 1. 创建一个扫描器，且扫描器中只有一个 USE
    Set token = getUseToken
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: index_hint + 一个非逗号
    ' ==============================
    ' 1. 创建一个扫描器，且扫描器中只有一个 USE + 一个非逗号元素
    Set token = getUseToken
    Set scanner = getObjArrayRefScanner(token, getLeftParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    ' ==============================
    ' 3. 扫描器内:  index_hint + [,]
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "use,"

    ' ==============================
    ' 4. 扫描器内: index_hint + 一个注释 + [,]
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, createShareLineCommentToken("--c1"), _
        getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use --c1"
    Debug.Assert strArr.Item(1) = ","

    ' ==============================
    ' 5. 扫描器内: index_hint + [,] (这个逗号后无法处理) + 一个注释
    ' ==============================
    Set token = getUseToken
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(token, getCommaToken, _
        createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.COMMA
    Debug.Assert elem.toString(config) = "use"

    ' ==============================
    ' 6. 扫描器内: index_hint + [,] + 一个注释 + index_hint
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getCommaToken, _
        createShareLineCommentToken("--c1"), _
        getForceToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use, --c1"
    Debug.Assert strArr.Item(1) = "force"

    ' ==============================
    ' 7. 扫描器内: index_hint + [,] + 一个注释 + index_hint + 一个非逗号
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getCommaToken, _
        createShareLineCommentToken("--c1"), _
        getForceToken, getLeftParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use, --c1"
    Debug.Assert strArr.Item(1) = "force"

    ' ==============================
    ' 8. 扫描器内: index_hint + [,] + index_hint + [,] + 一个非 index_hint
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getCommaToken, _
        getForceToken, getCommaToken, _
        getStrNameToken("x1") _
        )
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.COMMA

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use,"
    Debug.Assert strArr.Item(1) = "force"

    ' ==============================
    ' 9. 一个完整的 index_hint + [,] + 一个完整的 index_hint + [,] + KEY_UNION
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getUseToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), getRightParenthesisToken, _
        _
        getCommaToken, _
        _
        getForceToken, getKeyToken, _
        getForToken, getGroupByToken, _
        getLeftParenthesisToken, getStrNameToken("x3"), getCommaToken, getStrNameToken("x4"), getRightParenthesisToken, _
        _
        getCommaToken, _
        _
        getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHintList(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterCommaDeliMultiElem
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.COMMA

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for group by (x1, x2),"
    Debug.Assert strArr.Item(2) = "force key"
    Debug.Assert strArr.Item(3) = "    for group by (x3, x4)"

End Sub

' 测试: 匹配index_hint
Private Sub test_parseIndexHint()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器中只有一个 USE
    ' ==============================
    Set token = getUseToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器中只有一个 IGNORE
    ' ==============================
    Set token = getIgnoreToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 3. 扫描器中只有一个 FORCE
    ' ==============================
    Set token = getForceToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 4. 扫描器: USE + 一个非 KEY_INDEX、KEY_KEY 的元素
    ' ==============================
    Set token = getUseToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, getLeftParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    ' ==============================
    ' 5. 扫描器: USE + 一个 KEY_INDEX 的元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getIndexToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "use index"

    ' ==============================
    ' 6. 扫描器: USE + KEY_KEY
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "use key"

    ' ==============================
    ' 7. 扫描器: USE + KEY_KEY + 非For/非左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterTokenGroup
    Debug.Assert elem.toString(config) = "use key"

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    ' ==============================
    ' 8. 扫描器: USE + KEY_KEY + 一个 For
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, getForToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for"

    ' ==============================
    ' 9. 扫描器: USE + KEY_KEY + For + 一个非 KEY_JOIN/KEY_ORDER_BY/KEY_GROUP_BY
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv

    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for"

    ' ==============================
    ' 10. 扫描器: USE + KEY_KEY + For + KEY_JOIN
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getJoinToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for join"

    ' ==============================
    ' 11. 扫描器: USE + KEY_KEY + For + KEY_ORDER_BY
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getOrderByToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for order by"

    ' ==============================
    ' 12. 扫描器: USE + KEY_KEY + For + KEY_GROUP_BY
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getGroupByToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for group by"

    ' ==============================
    ' 13. 扫描器: USE + KEY_KEY + For + KEY_GROUP_BY + 一个非左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getGroupByToken, getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for group by"

    ' ==============================
    ' 14. 扫描器: USE + KEY_KEY + For + KEY_GROUP_BY + (
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getGroupByToken, getLeftParenthesisToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for group by ("

    ' ==============================
    ' 15. 扫描器: USE + KEY_KEY + For + KEY_GROUP_BY + ( + STR_NAME
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getGroupByToken, getLeftParenthesisToken, getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for group by (x1"

    ' ==============================
    ' 16. 扫描器: USE + KEY_KEY + For + KEY_GROUP_BY + ( + STR_NAME + [,]
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getGroupByToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for group by (x1,"

    ' ==============================
    ' 17. 扫描器: USE + KEY_KEY + For + KEY_GROUP_BY + (
    '     + STR_NAME + [,] + STR_NAME + ) + Union(用来结束处理)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUseToken, getKeyToken, _
        getForToken, getGroupByToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseIndexHint(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "use key"
    Debug.Assert strArr.Item(1) = "    for group by (x1, x2)"

End Sub

' 测试: 解析条件作用域
Private Sub test_parseConditionEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: where
    ' ==============================
    Set token = getWhereToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: having
    ' ==============================
    Set token = getHavingToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 3. 扫描器内: on
    ' ==============================
    Set token = getOnToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 4. 扫描器内: where + UNION
    '    测试没有解析出一个条件的情况
    ' ==============================
    Set token = getWhereToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 5. 扫描器内: where + STR_NAME + <= + STR_NAME
    '    解析一个条件
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"

    ' ==============================
    ' 6. 扫描器内: where + STR_NAME + <= + STR_NAME + UNION(强制停止)
    '    解析一个条件
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"), getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"

    ' ==============================
    ' 7. 扫描器内: where + STR_NAME + <= + STR_NAME
    '             + ( + (
    '    解析一个条件 + 测试多余的左括号可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"), getLeftParenthesisToken, getLeftParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"
    Debug.Assert strArr.Item(2) = "    ("
    Debug.Assert strArr.Item(3) = "    ("

    ' ==============================
    ' 8. 扫描器内: where + STR_NAME + <= + STR_NAME
    '             + ( + UNION
    '    解析一个条件 + 条件后面不是逻辑运算符--> 导致左括号被回收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"), getLeftParenthesisToken, getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"

    ' ==============================
    ' 9. 扫描器内: where + STR_NAME + <= + STR_NAME
    '             + ( + and
    '    解析一个条件 + 条件后面只剩下一个逻辑运算符，最后的逻辑运算符能否被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"), getLeftParenthesisToken, getAndToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"
    Debug.Assert strArr.Item(2) = "    ("
    Debug.Assert strArr.Item(3) = "    and"

    ' ==============================
    ' 10. 扫描器内: where + STR_NAME + <= + STR_NAME
    '             + ( + and + UNION
    '    解析一个条件 + 第二个条件除了逻辑运算符外无法解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"), getLeftParenthesisToken, getAndToken, getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"
    Debug.Assert strArr.Item(2) = "    ("
    Debug.Assert strArr.Item(3) = "    and"

    ' ==============================
    ' 11. 扫描器内: where
    '             + STR_NAME + <= +  STR_NAME
    '             + ( 一个注释 (多余的左括号)
    '             + ( + and + STR_NAME + <= + STR_NAME + )
    '    解析一个条件 + 解析第二个、包含边界括号的条件 + 吸收第二个条件中未吸收的、多余的左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, _
        getStrNameToken("x1"), getLeConditionOperator, getStrNameToken("x2"), _
        getLeftParenthesisToken, createShareLineCommentToken("--c1"), _
        getLeftParenthesisToken, getAndToken, getStrNameToken("x3"), getLeConditionOperator, getStrNameToken("x4"), getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"
    Debug.Assert strArr.Item(2) = "    ( --c1"
    Debug.Assert strArr.Item(3) = "    (and x3 <= x4)"

    ' ==============================
    ' 12. 扫描器内: where
    '             + STR_NAME + <= + STR_NAME
    '             + and
    '                  + STR_NAME + <=
    '                        + STR_NAME + [+]
    '                        + case
    '                        + when + STR_NAME + Then + STR_NAME
    '                        + end
    '    解析多行条件
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, _
        getStrNameToken("b1"), getLeConditionOperator, getStrNameToken("b2"), _
        _
        getAndToken, _
        _
        getStrNameToken("z1"), getLeConditionOperator, _
        _
        getStrNameToken("y1"), getPlusToken, _
        getCaseToken, _
        getWhenToken, getStrNameToken("x1"), getThenToken, getStrNameToken("x2"), _
        getEndToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    b1 <= b2"
    Debug.Assert strArr.Item(2) = "    and z1 <= y1 +"
    Debug.Assert strArr.Item(3) = "                  case"
    Debug.Assert strArr.Item(4) = "                      when x1 then x2"
    Debug.Assert strArr.Item(5) = "                  end"

    ' ==============================
    ' 13. 扫描器内: where
    '             + 一个表名STR_NAME + [].] + STR_NAME
    '                     + <= + 一个表名STR_NAME + [.] + STR_NAME
    '             + and
    '             + 一个表名STR_NAME + [.] + STR_NAME
    '                     + <= + 一个表名STR_NAME + [.] + STR_NAME
    '    带有表名的列的测试
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, _
        getStrNameToken("t1"), getPointToken, getStrNameToken("b1"), _
        getLeConditionOperator, getStrNameToken("t2"), getPointToken, getStrNameToken("b2"), _
        _
        getAndToken, _
        _
        getStrNameToken("t3"), getPointToken, getStrNameToken("b3"), _
        getLeConditionOperator, getStrNameToken("t4"), getPointToken, getStrNameToken("b4"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    t1.b1 <= t2.b2"
    Debug.Assert strArr.Item(2) = "    and t3.b3 <= t4.b4"

    ' ==============================
    ' 14. 扫描器内: where
    '             + 一个表名STR_NAME + [.] + STR_NAME
    '                     + <= + 一个表名STR_NAME + [.] + STR_NAME
    '             + and
    '             + not + exists
    '                 + (
    '                    + select + STR_NAME
    '                    + from + STR_NAME
    '                 + )
    '    测试: not exits 子查询
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, _
        getStrNameToken("t1"), getPointToken, getStrNameToken("b1"), _
        getLeConditionOperator, getStrNameToken("t2"), getPointToken, getStrNameToken("b2"), _
        _
        getAndToken, _
        _
        getNotToken, getExistsToken, _
        getLeftParenthesisToken, _
        getSelectToken, getStrNameToken("x1"), _
        getFromToken, getStrNameToken("x2"), _
        getRightParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 8
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    t1.b1 <= t2.b2"
    Debug.Assert strArr.Item(2) = "    and not exists ("
    Debug.Assert strArr.Item(3) = "            select"
    Debug.Assert strArr.Item(4) = "                x1"
    Debug.Assert strArr.Item(5) = "            from"
    Debug.Assert strArr.Item(6) = "                x2"
    Debug.Assert strArr.Item(7) = "        )"

    ' ==============================
    ' 15. 扫描器内: where 一个同行注释 + 一个单行注释
    '             + STR_NAME + <= + STR_NAME
    '             + ( + UNION
    '    测试 key[where] 后的同行注释可以被吸收到 key 后面
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"), getLeftParenthesisToken, getUnionToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseConditionEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "where --c1"
    Debug.Assert strArr.Item(1) = "    --c2"
    Debug.Assert strArr.Item(2) = "    x1 <= x2"
End Sub

' 测试: 匹配一个赋值语句
Private Sub test_parseAssignment()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 扫描器内: 空扫描器
    '    测试无法解析出任何内容 = 返回 null
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)
    scanner.toNext
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: UNION
    '    测试无法解析出任何内容 = 返回 null
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 3. 扫描器内: STR_NAME
    '    测试只有一个元素的条件
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignment
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1"

    ' ==============================
    ' 4. 扫描器内: 一个单行注释 + STR_NAME
    '    测试只有一个元素的条件
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(createSingleLineCommentToken("--c1"), getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignment
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = "x1"

    ' ==============================
    ' 5. 扫描器内: STR_NAME + UNION(强制停止)
    '    测试只有一个元素的条件 + 第一个元素后不是赋值运算符
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignment
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION
    Debug.Assert elem.toString(config) = "x1"

    ' ==============================
    ' 6. 扫描器内: STR_NAME + 一个同行注释 + 一个单行注释 + UNION(强制停止)
    '    测试只有一个元素的条件 + 第一个元素后面的同行注释可以吸收 + 第一个元素后不是赋值运算符，其后面的单行注释不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"), _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignment
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c2"
    Debug.Assert elem.toString(config) = "x1 --c1"

    ' ==============================
    ' 7. 扫描器内: STR_NAME + = + 一个同行注释 + 一个单行注释 + union
    '    只能吸收第一个元素 + 赋值运算符和其后面的同行注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"), getEqConditionOperator, _
        createShareLineCommentToken("--c1"), createSingleLineCommentToken("--c2"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignment
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "--c2"

    Debug.Assert elem.toString(config) = "x1 = --c1"

    ' ==============================
    ' 8. 扫描器内: STR_NAME + = + STR_NAME
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"), getEqConditionOperator, getStrNameToken("x2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignment
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "x1 = x2"

    ' ==============================
    ' 9. 扫描器内: 单行注释1 + STR_NAME + 同行注释2
    '            + 单行注释3 + [=] + 同行注释4
    '            + 单行注释5 + STR_NAME
    '    每个部分前的注释可以被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        createSingleLineCommentToken("--c1"), getStrNameToken("x1"), createShareLineCommentToken("--c2"), _
        _
        createSingleLineCommentToken("--c3"), getEqConditionOperator, createShareLineCommentToken("--c4"), _
        _
        createSingleLineCommentToken("--c5"), getStrNameToken("x2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignment
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 6
    Debug.Assert strArr.Item(0) = "--c1"
    Debug.Assert strArr.Item(1) = "x1 --c2"
    Debug.Assert strArr.Item(2) = "--c3"
    Debug.Assert strArr.Item(3) = "= --c4"
    Debug.Assert strArr.Item(4) = "--c5"
    Debug.Assert strArr.Item(5) = "x2"

    ' ==============================
    ' 10. 扫描器内: STR_NAME + = + STR_NAME + ) + 一个同行注释
    '     左括号栈: 一个左括号
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"), getEqConditionOperator, _
        getStrNameToken("x2"), getRightParenthesisToken, _
        createShareLineCommentToken("--c1"))
    Set stack = New  ObjStack
    stack.push getLeftParenthesisToken
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, stack)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "(x1 = x2) --c1"

    ' ==============================
    ' 11. 扫描器内:
    '                  STR_NAME
    '              + =
    '                  + STR_NAME + PLUS
    '                      + case
    '                      + when + STR_NAME + Then + STR_NAME
    '                      + end
    '     多行元素测试 + 表达式
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner( _
        getStrNameToken("x1"), _
        _
        getEqConditionOperator, _
        _
        getStrNameToken("y1"), getPlusToken, _
        getCaseToken, _
        getWhenToken, getStrNameToken("x3"), getThenToken, getStrNameToken("x4"), _
        getEndToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseAssignment(scanner, Nothing)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterAssignment
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    elem.setPrefixSpaceCount 0, config
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "x1 = y1 +"
    Debug.Assert strArr.Item(1) = "         case"
    Debug.Assert strArr.Item(2) = "             when x3 then x4"
    Debug.Assert strArr.Item(3) = "         end"

End Sub

' 测试: 匹配 limit
Private Sub test_parseLimitEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem

    ' ==============================
    ' 1. 扫描器内: Limit
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: Limit + 一个非数字/非 offset 元素
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, getLeftParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    ' ==============================
    ' 3. 扫描器内: Limit + 一个数字: LIMIT offset
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, createNumToken("12"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "limit 12"

    ' ==============================
    ' 4. 扫描器内: Limit + 一个数字 + 一个非数字/非 offset/非逗号元素: LIMIT offset
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, _
        createNumToken("12"), getLeftParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert elem.toString(config) = "limit 12"
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    ' ==============================
    ' 5. 扫描器内: Limit + 一个数字 + [,]: LIMIT offset,
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, _
        createNumToken("12"), getCommaToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert elem.toString(config) = "limit 12,"
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 6. 扫描器内: Limit + 一个数字 + [,] + 一个非offset/非数字元素: LIMIT offset,
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, _
        createNumToken("12"), getCommaToken, getLeftParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert elem.toString(config) = "limit 12,"
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    ' ==============================
    ' 7. 扫描器内: Limit + 一个数字 + offset: LIMIT row_count OFFSET
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, _
        createNumToken("12"), getOffsetToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert elem.toString(config) = "limit 12 offset"
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 8. 扫描器内: Limit + 一个数字 + offset + 一个非数字元素: LIMIT row_count OFFSET
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, _
        createNumToken("12"), getOffsetToken, getLeftParenthesisToken)

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert elem.toString(config) = "limit 12 offset"
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.LEFT_PARENTHESIS

    ' ==============================
    ' 9. 扫描器内: Limit + 一个数字 + [,] + 一个数字: LIMIT offset, row_count
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, _
        createNumToken("12"), getCommaToken, createNumToken("100"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert elem.toString(config) = "limit 12, 100"

    ' ==============================
    ' 10. 扫描器内: Limit + 一个数字 + offset + 一个数字: LIMIT row_count OFFSET offset
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, _
        createNumToken("12"), getOffsetToken, createNumToken("100"))

    ' 2. 执行测试方法
    Set elem = analyzer.parseLimitEnv(scanner)

    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterContainerCol
    Debug.Assert elem.toString(config) = "limit 12 offset 100"
End Sub

' 测试: 匹配order by
Private Sub test_parseOrderByEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: orderBy
    ' ==============================
    Set token = getOrderByToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert elem Is token
    Debug.Assert scanner.hasNext = False

    ' ==============================
    ' 2. 扫描器内: orderBy + STR_NAME
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, getStrNameToken("x1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1"

    ' ==============================
    ' 3. 扫描器内: orderBy + STR_NAME + ASC
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, getStrNameToken("x1"), getAscToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc"

    ' ==============================
    ' 4. 扫描器内: orderBy + STR_NAME + DESC
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, getStrNameToken("x1"), getDescToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 desc"

    ' ==============================
    ' 5. 扫描器内: orderBy + STR_NAME + [,]
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, getStrNameToken("x1"), getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1,"

    ' ==============================
    ' 6. 扫描器内: orderBy + STR_NAME + ASC + [,]
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, getStrNameToken("x1"), getAscToken, getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"

    ' ==============================
    ' 7. 扫描器内: orderBy + STR_NAME + ASC + [,] + STR_NAME
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2"

    ' ==============================
    ' 8. 扫描器内: orderBy + STR_NAME + ASC + [,] + STR_NAME + ASC
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getAscToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2 asc"

    ' ==============================
    ' 9. 扫描器内: orderBy + STR_NAME + ASC + [,] + STR_NAME + DESC
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getDescToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2 desc"

    ' ==============================
    ' 10. 扫描器内: orderBy + STR_NAME + ASC + [,] + STR_NAME + DESC + [,]
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getDescToken, getCommaToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2 desc,"

    ' ==============================
    ' 11. 扫描器内: orderBy
    '             + 一个单行注释 + STR_NAME + ASC + 一个同行注释
    '             + 一个单行注释 + [,] + 一个同行注释 + 一个单行注释
    '             + STR_NAME + DESC + 一个同行注释
    '             + 一个单行注释
    '     测试吸收各部分的注释，但是最后一个注释(属于下一个作用域的注释)不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        _
        createSingleLineCommentToken("--c1"), getStrNameToken("x1"), _
        getAscToken, createShareLineCommentToken("--c2"), _
        _
        createSingleLineCommentToken("--c3"), getCommaToken, _
        createShareLineCommentToken("--c4"), createSingleLineCommentToken("--c5"), _
        _
        getStrNameToken("x2"), getDescToken, createShareLineCommentToken("--c6"), _
        _
        createSingleLineCommentToken("--c7"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c7"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 7
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    x1 asc --c2"
    Debug.Assert strArr.Item(3) = "    --c3"
    Debug.Assert strArr.Item(4) = "    , --c4"
    Debug.Assert strArr.Item(5) = "    --c5"
    Debug.Assert strArr.Item(6) = "    x2 desc --c6"

    ' ==============================
    ' 12. 扫描器内: orderBy + STR_NAME + ASC + [,] + STR_NAME + 一个注释
    '     测试吸收注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2 --c1"

    ' ==============================
    ' 13. 扫描器内: orderBy + STR_NAME + ASC + [,] + STR_NAME + DESC + 一个注释
    '     测试吸收注释
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getDescToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2 desc --c1"

    ' ==============================
    ' 14. 扫描器内: orderBy + STR_NAME + UNION
    '     测试只有一个排序列
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1"

    ' ==============================
    ' 15. 扫描器内: orderBy + 一个同行注释 + STR_NAME + UNION
    '     测试只有一个排序列 + 吸收关键字后面的同行注释，并且和 orderby 连接到同一行
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, createShareLineCommentToken("--c1"), _
        getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "order by --c1"
    Debug.Assert strArr.Item(1) = "    x1"

    ' ==============================
    ' 16. 扫描器内: orderBy + 一个单行注释 + STR_NAME + UNION
    '     测试只有一个排序列 + orderby 后的单行注释会换行
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, createSingleLineCommentToken("--c1"), _
        getStrNameToken("x1"), getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseOrderByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    x1"
End Sub

' 测试: 匹配 group by
Private Sub test_parseGroupByEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim token As SqlToken
    Dim scanner As ObjArrayRefScanner
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: groupby + STR_NAME + ASC + [,] + STR_NAME + DESC + 一个注释
    '    group by 基础结构测试 (这里只测试完成的基础结构，基础结构中的每个内容的测试由 test_parseOrderByEnv 来做)
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getDescToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2 desc --c1"

    ' ==============================
    ' 2. 扫描器内: groupby + with
    '    WITH ROLLUP 测试
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, getWithToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    with"

    ' ==============================
    ' 3. 扫描器内: groupby + with + rollup
    '    WITH ROLLUP 测试
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, getWithToken, getRollupToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    with rollup"

    ' ==============================
    ' 4. 扫描器内: groupby + STR_NAME + ASC + with + rollup
    '    WITH ROLLUP 测试
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, _
        getStrNameToken("x1"), getAscToken, _
        getWithToken, getRollupToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    x1 asc with rollup"

    ' ==============================
    ' 5. 扫描器内: groupby + STR_NAME + ASC + with + rollup
    '    WITH ROLLUP 测试
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, _
        getStrNameToken("x1"), getAscToken, _
        getWithToken, getRollupToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    x1 asc with rollup"

    ' ==============================
    ' 6. 扫描器内: groupby + STR_NAME + ASC + with + UNION
    '    无法组成 WITH ROLLUP 的测试
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, _
        getStrNameToken("x1"), getAscToken, _
        getWithToken, getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_WITH

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    x1 asc"

    ' ==============================
    ' 7. 扫描器内: groupby + STR_NAME + ASC + UNION
    '    group by 基本内容后不是 with
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, _
        getStrNameToken("x1"), getAscToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    x1 asc"

    ' ==============================
    ' 8. 扫描器内: groupby + 一个同行注释 + STR_NAME + ASC + UNION
    '    测试: 吸收关键字后面的同行注释，并且和 groupby 连接到同一行
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, createShareLineCommentToken("--c1"), _
        getStrNameToken("x1"), getAscToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "group by --c1"
    Debug.Assert strArr.Item(1) = "    x1 asc"

    ' ==============================
    ' 9. 扫描器内: groupby + 一个单行注释 + STR_NAME + ASC + UNION
    '    测试: 后的单行注释会换行
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, createSingleLineCommentToken("--c1"), _
        getStrNameToken("x1"), getAscToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    x1 asc"

    ' ==============================
    ' 10. 扫描器内: groupby
    '             + 一个单行注释 + STR_NAME + ASC + 一个同行注释
    '             + 一个单行注释 + [,] + 一个同行注释 + 一个单行注释
    '             + STR_NAME + DESC + 一个同行注释
    '             + 一个单行注释
    '     测试吸收各部分的注释，但是最后一个注释(属于下一个作用域的注释)不会被吸收
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, _
        _
        createSingleLineCommentToken("--c1"), getStrNameToken("x1"), _
        getAscToken, createShareLineCommentToken("--c2"), _
        _
        createSingleLineCommentToken("--c3"), getCommaToken, _
        createShareLineCommentToken("--c4"), createSingleLineCommentToken("--c5"), _
        _
        getStrNameToken("x2"), getDescToken, createShareLineCommentToken("--c6"), _
        _
        createSingleLineCommentToken("--c7"))
    ' 2. 执行测试方法
    Set elem = analyzer.parseGroupByEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.SINGLE_LINE_COMMENT
    Debug.Assert token.Value = "--c7"

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 7
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    --c1"
    Debug.Assert strArr.Item(2) = "    x1 asc --c2"
    Debug.Assert strArr.Item(3) = "    --c3"
    Debug.Assert strArr.Item(4) = "    , --c4"
    Debug.Assert strArr.Item(5) = "    --c5"
    Debug.Assert strArr.Item(6) = "    x2 desc --c6"
End Sub